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

Add support for --unprivileged mode (MacOS only) #101

Closed
fujiapple852 opened this issue May 2, 2022 · 14 comments · Fixed by #638
Closed

Add support for --unprivileged mode (MacOS only) #101

fujiapple852 opened this issue May 2, 2022 · 14 comments · Fixed by #638

Comments

@fujiapple852
Copy link
Owner

fujiapple852 commented May 2, 2022

See for background.

The full set of ICMP sockets to support:

Family Direction Tracer Socket Field
IPv4 Both ICMP socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP) ip4_txrx_icmp_socket
IPv4 Both UDP socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) ip4_txrx_udp_socket
IPv6 Both ICMP socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6) ip6_txrx_icmp_socket
IPv6 Both UDP socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP) ip6_txrx_udp_socket
@fujiapple852 fujiapple852 added the enhancement New feature or request label May 2, 2022
@fujiapple852 fujiapple852 added this to the 0.5.0 milestone May 17, 2022
@fujiapple852 fujiapple852 modified the milestones: 0.5.0, 0.6.0 Jun 1, 2022
@fujiapple852 fujiapple852 modified the milestones: 0.6.0, 0.7.0 Aug 18, 2022
@fujiapple852 fujiapple852 removed this from the 0.7.0 milestone Dec 9, 2022
@fujiapple852
Copy link
Owner Author

For IPv4/UDP we use raw socket so we may set the identification field, which is needed for Dublin strategy which encodes the sequence in that field.

There is no setsockopt to set this field to avoid using a raw socket. That means that elevated permissions are needed for UDP, unlike ICMP where we can use the special "ping sockets" on Unix (Linux, macOS and others).

We may, therefore, need to support both raw and non-raw IPv4/UDP modes, the former being more powerful (supports Dublin strategy) and requiring elevated privileges and the latter being less powerful but not requiring elevated privileges.

@fujiapple852
Copy link
Owner Author

fujiapple852 commented Jul 22, 2023

For IPv4/ICMP the issue is that the platform byte order probing (PlatformIpv4FieldByteOrder) appears to require a raw socket (does not work with the above non-raw UDP socket on macOS, not needed on Linux, untested for others). Like UDP, we may therefore have to provide raw and non-raw variants for ICMP given the tradeoff. Specifically, raw allow for byte order probing but requires elevated privileges whereas non-raw must specify or assume the byte order but does not require elevated privileges.

Edit: resolved in #721

@fujiapple852
Copy link
Owner Author

fujiapple852 commented Jul 22, 2023

One further issue to resolve is that the TracerChannel currently always constructs both the ICMP send/recv sockets and the UDP send socket, regardless of the protocol the user has specified. Specifically:

let icmp_send_socket = make_icmp_send_socket(config.source_addr)?;
let udp_send_socket = make_udp_send_socket(config.source_addr)?;
let recv_socket = make_recv_socket(config.source_addr)?;

This limits us to requiring either both or neither to require elevated privileges. The solution to this problem is to only create the UDP send socket if running in UDP protocol mode, and only create the ICMP send socket in ICMP protocol mode. Note that the ICMP recv socket is needed and used by both.

Edit: resolved in #647

@fujiapple852
Copy link
Owner Author

We also need a way to be able to know, at startup, if the user is going to need to run with elevated privileges, such that we can error with a sensible message (currently handled in caps.rs).

If we implement fallback logic (try ping socket first, fallback to raw) then we have to move the error handling to later in the process and account for the fact it will occur on a separate thread.

This issue becomes simpler to solve if we force the user to be explicit are raw vs non-raw for both UDP and ICMP protocols.

@fujiapple852
Copy link
Owner Author

Running without requiring privileges could require the —unprivileged (-u) flag.

@c-git
Copy link
Collaborator

c-git commented Oct 21, 2023

This looks interesting. It would be nice to not need the flag, but if it makes the code materially more complicated without it. I think that's fine to require it.

@fujiapple852
Copy link
Owner Author

fujiapple852 commented Oct 22, 2023

Three reason why we want the user to be explicit about this:

  1. The behaviour of udp tracing differs between privileged and unprivileged modes. Specifically neither the Paris nor Dublin tracing strategies can be used in unprivileged mode as these involve manipulating the udp/ip headers which cannot be done without a raw socket (which requires privileges).
  2. The IPPROTO_ICMP socket is only available on a subset of platforms (i.e. most unix-like ones) but is not always enabled and will fail at runtime if not enabled. Falling back to the IPPROTO_RAW socket will fail without privileges.
  3. This is a change to the existing behaviour of Trippy and so it should be opt-in. The default will remain privileged mode and Trippy will error out on startup if the user attempts to use it without privileges as it does today.

@crrodriguez
Copy link

First, of all thanks for your software, been using it as replacment of mtr for a while with great, clear results.

about 1. That sounds like the kernel is missing an option to do this safely without the need of a privileged socket.. maybe talk to maintainers about it ?
what about requiring privileges only when OS does not support "ping sockets" AND the behaviour will not be the same due to the lack of number 1 item ?

At least on linux the highly desirable behaviour is to run without privileges always whenever possible. (according to the kernel changelog it is even possible to do so with plain sockets in some cases https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=45af29ca761c275e350cca659856bc56f1035ef9)

@c-git
Copy link
Collaborator

c-git commented Oct 22, 2023

@crrodriguez if @fujiapple852 is amenable to talking to the maintainers, is that something you would have to contacts to do?

Which ever option we finally go with, we should include in the error message for missing privileges that there is an unprivileged option available.

@fujiapple852
Copy link
Owner Author

fujiapple852 commented Oct 23, 2023

First, of all thanks for your software, been using it as replacment of mtr for a while with great, clear results.

Thanks @crrodriguez, I'm glad you are finding it useful.

That sounds like the kernel is missing an option to do this safely without the need of a privileged socket.. maybe talk to maintainers about it ?

It's a nice idea. To avoid the need to use a raw socket here we would need a way (i.e. a setsockopt call) to set the IPv4 header identifier field (for Dublin tracing) and the UDP header checksum field (for Paris tracing) on all platforms. Could these be exposed? In theory yes but some issues that come to mind immediately are hardware offloading and packet fragmentation and I imagine a bunch more. I think it would be a long hard road to get access to these across platforms! Saying that i'd be keen to explore this further with anyone who may be in a position to make it a reality.

what about requiring privileges only when OS does not support "ping sockets" AND the behaviour will not be the same due to the lack of number 1 item ?

The trouble is we don't know if the platform supports (or is configured to support) raw sockets until we try and open the socket.

The current logic (0.8.0) used by Trippy is:

  • Check if we are privileged, fail if we are not
  • Open raw socket, fail if we cannot

Note that the first check is redundant and exists only to allow a clear error message that links to https://github.com/fujiapple852/trippy#privileges vs showing some obscure socket open error.

The logic I am proposing (and have implemented in #638) is:

  • Check if we are privileged only if the user has selected privileged mode (default), fail if we are not
  • Open either the IPROTO_ICMP (if unprivileged mode) or RAW socket (privileged mode), fail if we can not

The alternative proposed logic that I was considering would be (ignoring Paris and Dublin edge cases):

  • Open the IPROTO_ICMP socket
    • If that fails, check if we are privileged
      • If we are privileged, open RAW socket, fail if we cannot
      • If we are unprivileged, fail

Whilst quite awkward to implement in Trippy today I think it would be possible. I not a fan of this approach however, there is a little bit to much "magic" here (try A, if it fails try B) and I feel it could lead to a confusing user experience.

There is also the question of the unsupported Paris and Dublin UDP cases, which would have to be factored into the above logic somehow.

we should include in the error message for missing privileges that there is an unprivileged option available.

This is the approach I have taken in the WIP implementation, where the unsupported cases are detected during parameter validation and we fail:

trip example.com --udp -R dublin -u
Error: Dublin tracing strategy cannot be used in unprivileged mode

The tui header also extends the protocol information to include the privilege level: protocol=udp(v4, classic, unprivileged)

Note that the current implementation does not preclude making it smarter in the future. Internally Trippy now has a PrivilegeMode:

/// The privilege mode.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum PrivilegeMode {
    /// Privileged mode.
    Privileged,
    /// Unprivileged mode.
    Unprivileged,
}

This is defaulted to PrivilegeMode::Privileged unless the --unprivileged (-u) flag is passed.

This setup would allow for adding additional PrivilegeMode variants in the future, such as Fallback which behave along the lines of the above. We could then expose an additional cmd line flag such as --privilege-mode <mode> where the fallback or other modes could be supplied.

We may even chose to change the default PrivilegeMode per platform. For example, this feature paves the way for the possibility of having an Android build in the future where unprivileged mode is essential (for Linux and other unixes we have better ways such as CAP_NET_RAW capabilities and setuid bit).

If you were willing it would be great if you could take WIP code (#638) for a spin and see how it feels in practise (note: doesn't support IPv6 yet and hasn't been tested on all platforms, but should be mostly functional). Update: does not work at all on Linux yet.

@fujiapple852
Copy link
Owner Author

fujiapple852 commented Oct 23, 2023

To my surprise, Linux does not allow setting IP_HDRINCL for IPPROTO_ICMP socket. This means the existing code to handle the icmp protocol will not work out-of-the-box for Linux as it did for MacOS.

Instead we must jump through some hoops to send EchoRequestPacket directly (with no added IP header) and use some additional socket options to extract the identifier/ttl from the responses.

@fujiapple852
Copy link
Owner Author

Below is an overview of support for unprivileged icmp across platforms. As a quick explanation of the columns:

IPPROTO_ICMP - A special datagram socket type which allows sending and receiving ICMP packets without requiring a RAW socket
IP_HDRINCL - A socket option which allows the userspace program to pass the IP header as well as the payload. This is normally only allowed for RAW sockets, however it is also supported for the IPPROTO_ICMP socket type on some platforms.
NET_CAP_RAW - A capability which may be assigned to an otherwise unprivileged program to allow the use of RAW sockets.

Platform IPPROTO_ICMP IPPROTO_ICMP + IP_HDRINCL NET_CAP_RAW Notes
MacOS Yes Yes No Fully supported
Linux Yes No Yes Whilst linux supports IPPROTO_ICMP it does not allow using it in conjunction with IP_HDRINCL. Instead Linux employs platform specific techniques as outlined in https://lwn.net/Articles/443051
Windows No No No Windows does not support IPPROTO_ICMP
NetBSD No No No Only RAW sockets supported according to https://man.netbsd.org/icmp.4
OpenBSD No No No Only RAW sockets supported according to https://man.openbsd.org/icmp
FreeBSD No No No Only RAW sockets supported according to https://man.freebsd.org/cgi/man.cgi?query=icmp&apropos=0&sektion=0&manpath=FreeBSD+15.0-CURRENT&arch=default&format=html

Therefore, at best, we can only support MacOS and Linux. However to support unprivileged icmp in Linux would require platform specific code which extends beyond the current socket abstraction layer and would require a signifiant refactor to support cleanly.

It is worth noting that Linux, unlike MacOS, is the only platform which support the NET_CAP_RAW capability which allows using RAW sockets from unprivileged processes, which goes a long way towards removing the need for this feature.

Therefore, for the initial implementation, this feature will be MacOS only.

@fujiapple852 fujiapple852 changed the title Support IPPROTO_ICMP (aka ping sockets) Add support for --unprivileged mode (MacOS only) Oct 29, 2023
@fujiapple852
Copy link
Owner Author

Created #741 follow up task to add support for Linux.

@c-git
Copy link
Collaborator

c-git commented Oct 29, 2023

I think that makes sense and I'm personally not sure it's worth doing it for linux at all. I don't know how other people feel but I personally feel that just granting the raw sockets permission only made me comfortable using trippy. I don't know how much value add there is for not requiring it at all for linux. IMO where it would matter the most is on Windows where you only have full admin or not and it doesn't work there anyway. So my personal opinion is that it's not worth the work to do it for linux.

@fujiapple852 fujiapple852 removed their assignment Nov 2, 2023
netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this issue Dec 1, 2023
[0.9.0] - 2023-11-30

Added

- Added support for tracing flows
  ([#776](fujiapple852/trippy#776))
- Added support for `icmp` extensions
  ([#33](fujiapple852/trippy#33))
- Added support for `MPLS` label stack class `icmp` extension
  objects ([#753](fujiapple852/trippy#753))
- Added support for [paris]
  (https://github.com/libparistraceroute/libparistraceroute) ECMP routing
  for `IPv6/udp` ([#749](fujiapple852/trippy#749))
- Added `--unprivileged` (`-u`) flag to allow tracing without elevated
  privileges (macOS
  only) ([#101](fujiapple852/trippy#101))
- Added `--tui-privacy-max-ttl` flag to hide host and IP details for low ttl
  hops ([#766](fujiapple852/trippy#766))
- Added `toggle-privacy` (default: `p`) key binding to show or hide private
  hops ([#823](fujiapple852/trippy#823))
- Added `toggle-flows` (default: `f`) key binding to show or hide tracing
  flows ([#777](fujiapple852/trippy#777))
- Added `--dns-resolve-all` (`-y`) flag to allow tracing to all IPs resolved
  from DNS lookup
  entry ([#743](fujiapple852/trippy#743))
- Added `dot` report mode (`-m dot`) to output hop graph in Graphviz `DOT`
  format ([#582](fujiapple852/trippy#582))
- Added `flows` report mode (`-m flows`) to output a list of all unique tracing
  flows ([#770](fujiapple852/trippy#770))
- Added `--icmp-extensions` (`-e`) flag for parsing `IPv4`/`IPv6` `icmp`
  extensions ([#751](fujiapple852/trippy#751))
- Added `--tui-icmp-extension-mode` flag to control how `icmp` extensions are
  rendered ([#752](fujiapple852/trippy#752))
- Added `--print-config-template` flag to output a template config
  file ([#792](fujiapple852/trippy#792))
- Added `--icmp` flag as a shortcut for `--protocol icmp`
  ([#649](fujiapple852/trippy#649))
- Added `toggle-help-alt` (default: `?`) key binding to show or hide
  help ([#694](fujiapple852/trippy#694))
- Added panic handing to Tui
  ([#784](fujiapple852/trippy#784))
- Added official Windows `scoop` package
  ([#462](fujiapple852/trippy#462))
- Added official Windows `winget` package
  ([#460](fujiapple852/trippy#460))
- Release `musl` Debian `deb` binary asset
  ([#568](fujiapple852/trippy#568))
- Release `armv7` Linux binary assets
  ([#712](fujiapple852/trippy#712))
- Release `aarch64-apple-darwin` (aka macOS Apple Silicon) binary
  assets ([#801](fujiapple852/trippy#801))
- Added additional Rust Tier 1 and Tier 2 binary assets
  ([#811](fujiapple852/trippy#811))

Changed

- [BREAKING CHANGE] `icmp` extension object data added to `json` and `stream`
  reports ([#806](fujiapple852/trippy#806))
- [BREAKING CHANGE] IPs field added to `csv` and all tabular
  reports ([#597](fujiapple852/trippy#597))
- [BREAKING CHANGE] Command line flags `--dns-lookup-as-info` and
  `--tui-preserve-screen` no longer require a boolean
  argument ([#708](fujiapple852/trippy#708))
- [BREAKING CHANGE] Default key binding for `ToggleFreeze` changed from `f`
  to `ctrl+f` ([#785](fujiapple852/trippy#785))
- Always render AS lines in hop details mode
  ([#825](fujiapple852/trippy#825))
- Expose DNS resolver module as part of `trippy` library
  ([#754](fujiapple852/trippy#754))
- Replaced unmaintained `tui-rs` crate with `ratatui` crate
  ([#569](fujiapple852/trippy#569))

Fixed

- Reverse DNS lookup not working in reports
  ([#509](fujiapple852/trippy#509))
- Crash on NetBSD during window resizing
  ([#276](fujiapple852/trippy#276))
- Protocol mismatch causes tracer panic
  ([#745](fujiapple852/trippy#745))
- Incorrect row height in Tui hop detail navigation view for hops with no
  responses ([#765](fujiapple852/trippy#765))
- Unnecessary socket creation in certain tracing modes
  ([#647](fujiapple852/trippy#647))
- Incorrect byte order in `IPv4` packet length calculation
  ([#686](fujiapple852/trippy#686))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants