Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

avoid allocations in SendTo and Quic #78591

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Sys
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct sockaddr_in
{
public byte sin_len;
public byte sin_family;
public ushort sin_port;
public fixed byte sin_addr[4];
private fixed byte sin_zero[8];

public void SetAddressFamily(int family)
{
sin_family = (byte)family;
}
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct sockaddr_in6
{
public byte sin_len;
public byte sin6_family;
public ushort sin6_port;
public uint sin6_flowinfo;
public fixed byte sin6_addr[16];
public uint sin6_scope_id;

public void SetAddressFamily(int family)
{
sin6_family = (byte)family;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

internal static partial class Interop
{
internal static partial class Sys
{
internal const int AF_INET = 2;
internal const int AF_INET6 = 28;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Sys
{
internal const int AF_INET = 2;
internal const int AF_INET6 = 10;

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct sockaddr_in
{
public ushort sin_family;
public ushort sin_port;
public fixed byte sin_addr[4];
private fixed byte sin_zero[8];

public void SetAddressFamily(int family)
{
sin_family = (ushort)family;
}
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct sockaddr_in6
{
public ushort sin6_family;
public ushort sin6_port;
public uint sin6_flowinfo;
public fixed byte sin6_addr[16];
public uint sin6_scope_id;

public void SetAddressFamily(int family)
{
sin6_family = (ushort)family;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

internal static partial class Interop
{
internal static partial class Sys
{
internal const int AF_INET = 2;
internal const int AF_INET6 = 30;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Sys
{
internal const int AF_INET = 2;
internal const int AF_INET6 = 23;

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct sockaddr_in
{
public ushort sin_family;
public ushort sin_port;
public fixed byte sin_addr[4];
private fixed byte sin_zero[8];

public void SetAddressFamily(int family)
{
sin_family = (ushort)family;
}
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct sockaddr_in6
{
public ushort sin6_family;
public ushort sin6_port;
public uint sin6_flowinfo;
public fixed byte sin6_addr[16];
public uint sin6_scope_id;

public void SetAddressFamily(int family)
{
sin6_family = (ushort)family;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ private static unsafe partial SocketError WSASendTo(
int bufferCount,
out int bytesTransferred,
SocketFlags socketFlags,
IntPtr socketAddress,
byte* socketAddress,
int socketAddressSize,
NativeOverlapped* overlapped,
IntPtr completionRoutine);
Expand All @@ -29,7 +29,7 @@ internal static unsafe SocketError WSASendTo(
int bufferCount,
out int bytesTransferred,
SocketFlags socketFlags,
IntPtr socketAddress,
byte* socketAddress,
int socketAddressSize,
NativeOverlapped* overlapped,
IntPtr completionRoutine)
Expand All @@ -47,7 +47,7 @@ internal static unsafe SocketError WSASendTo(
int bufferCount,
[Out] out int bytesTransferred,
SocketFlags socketFlags,
IntPtr socketAddress,
byte* socketAddress,
int socketAddressSize,
NativeOverlapped* overlapped,
IntPtr completionRoutine)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,14 @@ internal static unsafe partial int sendto(
SocketFlags socketFlags,
byte[] socketAddress,
int socketAddressSize);

[LibraryImport(Interop.Libraries.Ws2_32, SetLastError = true)]
internal static unsafe partial int sendto(
SafeSocketHandle socketHandle,
byte* pinnedBuffer,
int len,
SocketFlags socketFlags,
ReadOnlySpan<byte> socketAddress,
int socketAddressSize);
}
}
150 changes: 150 additions & 0 deletions src/libraries/Common/src/System/Net/SockAddr.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Net.Sockets;
using System.Runtime.InteropServices;

namespace System.Net.Sockets
{
[StructLayout(LayoutKind.Explicit)]
internal partial struct SockAddr
{
internal const int IPv4AddressSize = 4;
internal const int IPv6AddressSize = 16;

private static ReadOnlySpan<byte> V4MappedPrefix=> new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};

[FieldOffset(0)]
public Interop.Sys.sockaddr_in Ipv4;
[FieldOffset(0)]
public Interop.Sys.sockaddr_in6 Ipv6;
Comment on lines +18 to +21
Copy link
Member

@antonfirsov antonfirsov Nov 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We historically deal in our native PAL with these kind of concerns. I don't know enough context to decide if it's safe and good for us to reimplement the mapping in managed code for all the platforms.

Instead of defining structures, we could just hold into a fixed byte data[IPv6AddressSize] and let SocketAddressPal to do the dirty work. I think the cost of the extra shared library function call would be negligible.

@stephentoub @tmds thoughts?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not just the call, it is also pinning

public static unsafe AddressFamily GetAddressFamily(ReadOnlySpan<byte> buffer)
{
AddressFamily family;
Interop.Error err;
fixed (byte* rawAddress = buffer)
{
err = Interop.Sys.GetAddressFamily(rawAddress, buffer.Length, (int*)&family);
}

for something that would be easy to do in managed code. I feel the sockaddr is unlikely to change for platforms and families we care about. If anything, I would keep code to get size of sockaddr_storage in native so we do not need to track addition of new protocols.

And we do it for Quic already anyway. I feel it would be nice if we can share more code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is also pinning

We can avoid it by introducing non-pinning overloads, since SockAddr is created on the stack, or is expected to be pre-pinned in async cases.

it would be nice if we can share more code.

System.Net.Quic can also adapt SocketAddressPal. We would end up having fewer LOC.

Copy link
Member

@tmds tmds Nov 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the pinning cost worth the duplication?

If you're adding the mapping in managed code, then some existing users of {Get/Set}IPv{4,6}Address can probably also use it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pinning with fixed is close to free unless there's a GC that occurs concurrently, and that's not particularly likely with a really fast call like this one. Plus if the thing being pinned isn't actually on the heap, the cost during a GC that does occur is also minimal. I wouldn't duplicate a lot of code to avoid it.


public int Family => Ipv4.sin_family;

public unsafe SockAddr(IPEndPoint endPoint) : this(endPoint, endPoint.Address.AddressFamily)
{
}

public unsafe SockAddr(IPEndPoint endPoint, AddressFamily addressFamily)
{
if (addressFamily == endPoint.Address.AddressFamily)
{
if (endPoint.AddressFamily == AddressFamily.InterNetwork)
{
Ipv4.sin_family = Interop.Sys.AF_INET;
Ipv4.sin_port = (ushort)IPAddress.HostToNetworkOrder((short)endPoint.Port);
Span<byte> address = MemoryMarshal.CreateSpan(ref Ipv4.sin_addr[0], IPv4AddressSize);
if (!endPoint.Address.TryWriteBytes(address, out int bytesWritten) || bytesWritten != IPv4AddressSize)
{
throw new SocketException((int)SocketError.InvalidArgument);
}
}
else if (endPoint.AddressFamily == AddressFamily.InterNetworkV6)
{
Ipv6.sin6_family = Interop.Sys.AF_INET6;
Ipv6.sin6_port = (ushort)IPAddress.HostToNetworkOrder((short)endPoint.Port);
Span<byte> address = MemoryMarshal.CreateSpan(ref Ipv6.sin6_addr[0], IPv6AddressSize);
if (!endPoint.Address.TryWriteBytes(address, out int bytesWritten) || bytesWritten != IPv6AddressSize)
{
throw new SocketException((int)SocketError.InvalidArgument);
}
}
else
{
// Next IP version???
throw new SocketException((int)SocketError.AddressFamilyNotSupported);
}
}
else if (endPoint.Address.AddressFamily == AddressFamily.InterNetwork && addressFamily == AddressFamily.InterNetworkV6)
{
// Map IPv4 to IPv4. This is currently only supported cases when AddressFamily does not match.
Ipv6.sin6_family = Interop.Sys.AF_INET6;
Ipv6.sin6_port = (ushort)IPAddress.HostToNetworkOrder((short)endPoint.Port);
Span<byte> address = MemoryMarshal.CreateSpan(ref Ipv6.sin6_addr[0], IPv6AddressSize);

V4MappedPrefix.CopyTo(address);
if (!endPoint.Address.TryWriteBytes(address.Slice(V4MappedPrefix.Length), out int bytesWritten) || bytesWritten != IPv4AddressSize)
{
throw new SocketException((int)SocketError.InvalidArgument);
}
}
else
{
throw new SocketException((int)SocketError.AddressFamilyNotSupported);
}
}

public AddressFamily AddressFamily
{
get
{
switch (Family)
{
case Interop.Sys.AF_INET: return AddressFamily.InterNetwork;
case Interop.Sys.AF_INET6: return AddressFamily.InterNetworkV6;
default:
throw new SocketException((int)SocketError.AddressFamilyNotSupported);
}
}
}

public unsafe int Length
{
get
{
switch (Family)
{
case Interop.Sys.AF_INET: return sizeof(Interop.Sys.sockaddr_in);
case Interop.Sys.AF_INET6: return sizeof(Interop.Sys.sockaddr_in6);
default:
throw new SocketException((int)SocketError.AddressFamilyNotSupported);
}
}
}

public unsafe ReadOnlySpan<byte> AsBytes()
{
return MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref this, 1)).Slice(0, Length);
}

public ushort GetPort()
{
if (AddressFamily == AddressFamily.InterNetworkV6)
{
return (ushort)IPAddress.NetworkToHostOrder((short)Ipv6.sin6_port);
}
else if (AddressFamily == AddressFamily.InterNetwork)
{
return (ushort)IPAddress.NetworkToHostOrder((short)Ipv4.sin_port);
}
else
{
throw new SocketException((int)SocketError.AddressFamilyNotSupported);
}
}

public unsafe IPAddress GetIPAddress()
{
if (AddressFamily == AddressFamily.InterNetworkV6)
{
return new IPAddress(MemoryMarshal.CreateSpan(ref Ipv6.sin6_addr[0], IPv6AddressSize), Ipv6.sin6_scope_id);
}
else if (AddressFamily == AddressFamily.InterNetwork)
{
return new IPAddress(MemoryMarshal.CreateSpan(ref Ipv4.sin_addr[0], IPv4AddressSize));
}
else
{
throw new SocketException((int)SocketError.AddressFamilyNotSupported);
}
}

public IPEndPoint GetIPEndPoint()
{
return new IPEndPoint(GetIPAddress(), GetPort());
}

public void SetAddressFamily(AddressFamily family) => Ipv4.SetAddressFamily((int)family);
}
}
Loading