dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.28k stars 4.73k forks source link

Socket.DontFragment can't be set on IPv6 DualMode sockets #76410

Closed PJB3005 closed 5 months ago

PJB3005 commented 2 years ago

Description

While DontFragment doesn't make much sense on IPv6 itself, it is still useful for dual-stack sockets (an IPv6 socket that can handle IPv4 traffic too, through ::FFFF:x.x.x.x IPv6 addresses).

Manually setting it via SetSocketOption(SocketOptionLevel.IP, SocketOptionName.DontFragment, true) does work. The problem appears to be a rough top-level check in the DontFragment property itself, nothing lower-level: https://github.com/dotnet/runtime/blob/8ff1bd04dfce1ca7e80401053b8983e22798a29d/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs#L652

On both Windows and Linux, the flags are correctly respected when sending IPv4 traffic out of the IPv6 socket (at least, if my Wireshark isn't lying).

Reproduction Steps

using var s = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);

s.DualMode = true;

s.Bind(IPEndPoint.Parse("[::]:1212"));
s.DontFragment = true;

Expected behavior

Don't fragment to be set on IPv4 sockets sent from the dual-stack socket.

Actual behavior

Unhandled exception. System.NotSupportedException: This protocol version is not supported.

Regression?

No response

Known Workarounds

SetSocketOption(SocketOptionLevel.IP, SocketOptionName.DontFragment, true); works great.

Configuration

No response

Other information

No response

ghost commented 2 years ago

Tagging subscribers to this area: @dotnet/ncl See info in area-owners.md if you want to be subscribed.

Issue Details
### Description While `DontFragment` doesn't make much sense on IPv6 itself, it is still useful for dual-stack sockets (an IPv6 socket that can handle IPv4 traffic too, through `::FFFF:x.x.x.x` IPv6 addresses). Manually setting it via `SetSocketOption(SocketOptionLevel.IP, SocketOptionName.DontFragment, true)` does work. The problem appears to be a rough top-level check in the `DontFragment` property itself, nothing lower-level: https://github.com/dotnet/runtime/blob/8ff1bd04dfce1ca7e80401053b8983e22798a29d/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs#L652 ### Reproduction Steps ```cs using var s = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); s.DualMode = true; s.Bind(IPEndPoint.Parse("[::]:1212")); s.DontFragment = true; ``` ### Expected behavior Don't fragment to be set on IPv4 sockets sent from the dual-stack socket. ### Actual behavior ``` Unhandled exception. System.NotSupportedException: This protocol version is not supported. ``` ### Regression? _No response_ ### Known Workarounds `SetSocketOption(SocketOptionLevel.IP, SocketOptionName.DontFragment, true);` works great. ### Configuration _No response_ ### Other information _No response_
Author: PJB3005
Assignees: -
Labels: `area-System.Net.Sockets`, `untriaged`
Milestone: -
wfurt commented 2 years ago

How did you test it @PJB3005? Using the Socket.SetRawSocketOption? It seems reasonable to relax the checks if DualMode is enabled and if it works on majority of OSes.

PJB3005 commented 2 years ago

@wfurt Just SetSocketOption(SocketOptionLevel.IP, SocketOptionName.DontFragment, true);. It lines up on Windows and PAL maps it to IP_PMTUDISC_DONT/IP_PMTUDISC_DO on Linux. This is basically just one call down from Socket.DontFragment, with one protocol check removed.

It doesn't work on mac, but that's because macOS outright doesn't support setting don't-fragment in the first place. Socket.DontFragment doesn't work there ever, so it's not really dependent on dual-mode or whatever. (Big Sur apparently finally has a flag for it, but somebody who was helping me test this couldn't get it to work I don't think.)

I have no idea about BSD.

PJB3005 commented 2 years ago

For reference, here are some bits of code you can use to verify that it works:

// Test sending IPv4 packets from IPv6.

using var s = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);

s.DualMode = true;
s.Bind(IPEndPoint.Parse("[::]:1212"));

var targetEP = IPEndPoint.Parse("[::ffff:1.1.1.1]:1212");

s.SendTo(new byte[1234], targetEP);
s.SendTo(new byte[9000], targetEP);

s.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.DontFragment, true);

s.SendTo(new byte[1234], targetEP);
s.SendTo(new byte[9000], targetEP);
// Just plain IPv4.
using var s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

s.Bind(IPEndPoint.Parse("0.0.0.0:1212"));

var targetEP = IPEndPoint.Parse("1.1.1.1:1212");

s.SendTo(new byte[1234], targetEP);
s.SendTo(new byte[9000], targetEP);

s.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.DontFragment, true);

s.SendTo(new byte[1234], targetEP);
s.SendTo(new byte[9000], targetEP);
karelz commented 2 years ago

Triage: Makes sense to allow the flag on IPv6 for cases when it encapsulated IPv4. Should be easy to do. But not a priority -> Future. We would welcome a contribution though!