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

Ping.Send buffer is not really sent on linux running as non-root #62458

Closed
svc-user opened this issue Dec 6, 2021 · 6 comments · Fixed by #64625
Closed

Ping.Send buffer is not really sent on linux running as non-root #62458

svc-user opened this issue Dec 6, 2021 · 6 comments · Fixed by #64625
Labels
area-System.Net bug help wanted [up-for-grabs] Good issue for external contributors os-linux Linux OS (any supported distro)
Milestone

Comments

@svc-user
Copy link

svc-user commented Dec 6, 2021

Description

When creating a new instance of the Ping class and calling the Send method providing a buffer, the buffer is not put into the ICMP message that's sent over the network unless the program is run as root.

Instead the runtime simply forwards the call to the ping binary on the system, applying the -s parameter that tells ping how large a payload should be sent. From here it's the systems ping command actually generating the payload and not the payload defined in the C# code.

Reproduction Steps

Create a new console application with dotnet new console -n fancy-project-name and cd into the directory. Use the following code in Program.cs

using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;

if (args[0] == "/Client")
{
    Client();
}
else if (args[0] == "/Server")
{
    Server();
}

static void Client()
{
    Ping pinger = new();

    byte[] buff = new byte[123];
    for (int i = 0; i < buff.Length; i++)
    {
        buff[i] = (byte)'A'; // fill buff with something easily recognizable
    }

    pinger.Send(IPAddress.Parse("127.0.0.1"), 10, buff);
}

static void Server()
{
    Socket s = new(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
    s.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0));

    Console.WriteLine("Listening for ICMP message.");

    byte[] buff = new byte[1024];
    var r = s.Receive(buff);

    Console.WriteLine($"Received {r}bytes.");
    for (int i = 0; i < r; i++)
    {
        Console.Write(buff[i].ToString("x02") + " ");
        if (i != 0 && i % 16 == 0)
        {
            Console.WriteLine();
        }
    }
    Console.WriteLine();
}

Build the executable with dotnet build and cd into the build output folder. Open a second terminal in the same folder.
In terminal window A run the server-part as root sudo ./fancy-project-name /Server.
In terminal window B run the client-part as non-root ./fancy-project-name /Client. Notice how the output is not the hex-code for 'A'.
Rerun the server as root in terminal window A and now rerun the client as root in terminal window B. This time the server prints the expected bytes.

Expected behavior

The supplied buffer is sent along with the ICMP echo message.

Actual behavior

The wrong payload is sent along with the ICMP echo message.

Regression?

No response

Known Workarounds

Running any "Client" program as root sends along the correct buffer.

Configuration

Output of dotnet --info

.NET SDK (reflecting any global.json):
 Version:   6.0.100
 Commit:    9e8b04bbff

Runtime Environment:
 OS Name:     debian
 OS Version:  
 OS Platform: Linux
 RID:         debian-x64
 Base Path:   /usr/share/dotnet/sdk/6.0.100/

Host (useful for support):
  Version: 6.0.0
  Commit:  4822e3c3aa

.NET SDKs installed:
  2.2.402 [/usr/share/dotnet/sdk]
  5.0.403 [/usr/share/dotnet/sdk]
  6.0.100 [/usr/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.All 2.2.8 [/usr/share/dotnet/shared/Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.2.8 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.12 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.0 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.2.8 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.12 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.0 [/usr/share/dotnet/shared/Microsoft.NETCore.App]

Other information

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Net untriaged New issue has not been triaged by the area owner labels Dec 6, 2021
@ghost
Copy link

ghost commented Dec 6, 2021

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

Issue Details

Description

When creating a new instance of the Ping class and calling the Send method providing a buffer, the buffer is not put into the ICMP message that's sent over the network unless the program is run as root.

Instead the runtime simply forwards the call to the ping binary on the system, applying the -s parameter that tells ping how large a payload should be sent. From here it's the systems ping command actually generating the payload and not the payload defined in the C# code.

Reproduction Steps

Create a new console application with dotnet new console -n fancy-project-name and cd into the directory. Use the following code in Program.cs

using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;

if (args[0] == "/Client")
{
    Client();
}
else if (args[0] == "/Server")
{
    Server();
}

static void Client()
{
    Ping pinger = new();

    byte[] buff = new byte[123];
    for (int i = 0; i < buff.Length; i++)
    {
        buff[i] = (byte)'A'; // fill buff with something easily recognizable
    }

    pinger.Send(IPAddress.Parse("127.0.0.1"), 10, buff);
}

static void Server()
{
    Socket s = new(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
    s.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0));

    Console.WriteLine("Listening for ICMP message.");

    byte[] buff = new byte[1024];
    var r = s.Receive(buff);

    Console.WriteLine($"Received {r}bytes.");
    for (int i = 0; i < r; i++)
    {
        Console.Write(buff[i].ToString("x02") + " ");
        if (i != 0 && i % 16 == 0)
        {
            Console.WriteLine();
        }
    }
    Console.WriteLine();
}

Build the executable with dotnet build and cd into the build output folder. Open a second terminal in the same folder.
In terminal window A run the server-part as root sudo ./fancy-project-name /Server.
In terminal window B run the client-part as non-root ./fancy-project-name /Client. Notice how the output is not the hex-code for 'A'.
Rerun the server as root in terminal window A and now rerun the client as root in terminal window B. This time the server prints the expected bytes.

Expected behavior

The supplied buffer is sent along with the ICMP echo message.

Actual behavior

The wrong payload is sent along with the ICMP echo message.

Regression?

No response

Known Workarounds

Running any "Client" program as root sends along the correct buffer.

Configuration

Output of dotnet --info

.NET SDK (reflecting any global.json):
 Version:   6.0.100
 Commit:    9e8b04bbff

Runtime Environment:
 OS Name:     debian
 OS Version:  
 OS Platform: Linux
 RID:         debian-x64
 Base Path:   /usr/share/dotnet/sdk/6.0.100/

Host (useful for support):
  Version: 6.0.0
  Commit:  4822e3c3aa

.NET SDKs installed:
  2.2.402 [/usr/share/dotnet/sdk]
  5.0.403 [/usr/share/dotnet/sdk]
  6.0.100 [/usr/share/dotnet/sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.All 2.2.8 [/usr/share/dotnet/shared/Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.2.8 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.12 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.0 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.2.8 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.12 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.0 [/usr/share/dotnet/shared/Microsoft.NETCore.App]

Other information

No response

Author: bdoner
Assignees: -
Labels:

area-System.Net, untriaged

Milestone: -

@wfurt wfurt added the os-linux Linux OS (any supported distro) label Dec 6, 2021
@wfurt
Copy link
Member

wfurt commented Dec 6, 2021

While some improvements may be possible I don't think it is fixable as the ping utility has very limited options to specify custom payload.
It feels like best option would be to improve documentation or throw PNSP.

@am11
Copy link
Member

am11 commented Dec 7, 2021

Another option is to give the process cap_net_raw:
sudo setcap cap_net_raw+ep bin/Release/net6.0/publish/fancy-app
then run with /Client arg without sudo (unprivileged).

I think exception in this case might be more helpful and improve the user experience; with message calling out "run program with privileged user or grant cap_net_raw using setcap(8)" (for OS: Linux). FreeBSD, Solaris / illumos etc. refer to these capabilities as privileges, for which implementation can be extended at later point.

@karelz
Copy link
Member

karelz commented Dec 7, 2021

Triage: If we can't send the payload, we should throw PNSE instead of no-op (today). The exception should have reasonable message as mentioned above.

@karelz karelz added bug help wanted [up-for-grabs] Good issue for external contributors and removed untriaged New issue has not been triaged by the area owner labels Dec 7, 2021
@karelz karelz added this to the Future milestone Dec 7, 2021
@karelz karelz added this to To do in Networking ramp-up via automation Dec 7, 2021
@svc-user
Copy link
Author

svc-user commented Dec 7, 2021

Thanks for chiming in. First I'd like to ask what a PSNE and a PNSP exception is. I can't figure it out 😅

Documentation is obviously a very good start. I first developed my application on windows, on which everything was fine. To then move to my Debian laptop where things all of a sudden where not so fine. A remark about the difference in behavior on the MSDN page would've been of great help.

I think an exception thrown is a fine solution in the case of a buffer being passed along. If I just wanted to check if something is up I don't care (as a developer) that the native ping utility transparently is invoked for me. That solution also works just fine (for a different scenario than mine).

@am11: that seems like a "correct" workaround. For now running as sudo is possible in my case, so that's what I'll probably continue doing, but I'll definitely try out your trick. Haven't used capabilities on executables on Linux before - it's all a learning experience!

@wfurt
Copy link
Member

wfurt commented Dec 7, 2021

PNSE (PNSP) -> PlatformNotSupportedException
The capabilities or not that well known but they allow to grant more fine-grained privilege beside super power root.

@rzikm rzikm self-assigned this Feb 1, 2022
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Feb 1, 2022
Networking ramp-up automation moved this from To do to Done Feb 3, 2022
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Feb 3, 2022
@rzikm rzikm removed their assignment Feb 14, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Mar 16, 2022
@karelz karelz modified the milestones: Future, 7.0.0 Apr 8, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Net bug help wanted [up-for-grabs] Good issue for external contributors os-linux Linux OS (any supported distro)
Projects
Development

Successfully merging a pull request may close this issue.

5 participants