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

Linux: UDP listen: Receive broadcasts (mimic Windows behavior) #149

Merged
merged 2 commits into from
Mar 23, 2023

Conversation

kgraefe
Copy link
Contributor

@kgraefe kgraefe commented Mar 22, 2023

On Windows, when listening on a specific IP address, you also receive corresponding subnet broadcasts and global broadcasts (Ipv4Addr::BROADCAST) received on the interface matching the IP.

In order to mimic that behavior on Linux, this change adds ingress filters to UDP/Listen so one can listen on Ipv4Addr::UNSPECIFIED but drop any packet that is not received on the specified interface or any of the specified IP addresses.

@lemunozm As I said in #142 I'm not sure if that's too much code for a too specific problem to be merged upstream. But I'll leave it here for you to decide.

@kgraefe kgraefe changed the title Udp listen ingress filter Linux: UDP listen: Add ingress filters Mar 22, 2023
@lemunozm
Copy link
Owner

Ok, I'll check later, but I think your PR goes with the message-io philosophy of abstracting different platform behaviors

@lemunozm
Copy link
Owner

I mean, with this change, a Linux user can achieve the same behavior that a Windows user, isn't it?

@kgraefe
Copy link
Contributor Author

kgraefe commented Mar 22, 2023

I mean, with this change, a Linux user can achieve the same behavior that a Windows user, isn't it?

yes

@lemunozm
Copy link
Owner

I'm ok with this PR if we extend the option docs to highlight just that.

@kgraefe
Copy link
Contributor Author

kgraefe commented Mar 22, 2023

but in the application I still have to use compile conditions:

// Listen on any IP but filter to only allow packets incoming on the specific interface and
// with one of the following destination addresses:
// - the interface's local IP,
// - the corresponding subnet broadcast IP, or
// - the global broadcast IP
#[cfg(target_os = "linux")]
let (socket_id, _) = node_handler.network().listen_with(
    TransportListen::Udp(UdpListenConfig {
        broadcast: true,
        ingress_addresses: vec![
            interface.local_ip.into(),
            interface.broadcast_ip.into(),
            Ipv4Addr::BROADCAST.into(),
        ],
        ingress_interface: Some(interface.index),
        ..Default::default()
    }),
    SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port),
)?;

// Windows does all of the above automatically for sockets listening on the interface's
// local IP.
#[cfg(target_os = "windows")]
let (socket_id, _) = node_handler.network().listen_with(
    TransportListen::Udp(UdpListenConfig {
        broadcast: true,
        ..Default::default()
    }),
    SocketAddrV4::new(interface.local_ip, port),
)?;

@kgraefe
Copy link
Contributor Author

kgraefe commented Mar 22, 2023

where interface is of a custom type with the information retrieved from nix::ifaddrs::getifaddrs() and nix::net::if_::if_nametoindex()

@kgraefe
Copy link
Contributor Author

kgraefe commented Mar 22, 2023

maybe I can rework that to a flag mimic_windows or sth. (I don't have a good name yet) where message-io tries to figure out the ingress filter parameters internally

@lemunozm
Copy link
Owner

lemunozm commented Mar 22, 2023

I really like the last idea! In that way, your app code will be really simple; that is what we looking for.

If the nix dependency is significantly big or annoying, it could exist under a feature. But for now, I think we can live with it.

@lemunozm
Copy link
Owner

lemunozm commented Mar 22, 2023

By the way, feel free to add your project to the README open-source list if you want.

@kgraefe kgraefe force-pushed the udp-listen-ingress-filter branch 2 times, most recently from 6df858a to 97eb83a Compare March 22, 2023 12:53
Signed-off-by: Konrad Gräfe <kgraefe@paktolos.net>
@kgraefe
Copy link
Contributor Author

kgraefe commented Mar 23, 2023

I'm done :-)

Comment on lines 327 to 328
#[cfg(target_os = "linux")]
fn accept(&self, mut accept_remote: impl FnMut(AcceptedType<'_, Self::Remote>)) {
Copy link
Owner

Choose a reason for hiding this comment

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

I get a little lost here; why the Linux behavior changes so much now?

Copy link
Contributor Author

@kgraefe kgraefe Mar 23, 2023

Choose a reason for hiding this comment

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

The reason is that I need to use nix::sys::socket::recvmsg() in order to pass the control_buffer which receives the ingress IP address. And as that's the core of the function it affects the rest of the function as well.

Copy link
Owner

Choose a reason for hiding this comment

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

Ok, I see.

I'm thinking, that maybe we can use the non-linux accept() for all platforms (as before), and only if the ingress config is enabled and we are in linux run this accept() as a special case.

The main advantage of this is that the normal use case (99% of time) will use the usual accept() which are probably a little bit faster and simple to debug/understand if, in the future, any user finds an issue.

@@ -231,6 +324,66 @@ impl Local for LocalResource {
}
}

#[cfg(target_os = "linux")]
fn accept(&self, mut accept_remote: impl FnMut(AcceptedType<'_, Self::Remote>)) {
let mut input_buffer = vec![0; MAX_LOCAL_PAYLOAD_LEN];
Copy link
Owner

Choose a reason for hiding this comment

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

Do we really need this to be a vector? Could it be an array to avoid allocations?

Copy link
Contributor Author

@kgraefe kgraefe Mar 23, 2023

Choose a reason for hiding this comment

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

probably not, I will change that

@kgraefe kgraefe changed the title Linux: UDP listen: Add ingress filters Linux: UDP listen: Receive broadcasts (mimic Windows behavior) Mar 23, 2023
@kgraefe kgraefe requested a review from lemunozm March 23, 2023 12:19
Copy link
Owner

@lemunozm lemunozm left a comment

Choose a reason for hiding this comment

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

Looks good! I left a last minor comment change, but not mandatory.

Comment on lines 374 to 382
#[cfg(target_os = "linux")]
if !self.ingress_addresses.is_empty() {
self.accept_filtered(accept_remote);
return
}
Copy link
Owner

Choose a reason for hiding this comment

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

Cool! 🚀

Comment on lines 289 to 303
#[cfg(target_os = "linux")]
match addr {
SocketAddr::V4 { .. } => {
socket::setsockopt(socket.as_raw_fd(), sockopt::Ipv4PacketInfo, &true)?
}
SocketAddr::V6 { .. } => {
socket::setsockopt(socket.as_raw_fd(), sockopt::Ipv6RecvPacketInfo, &true)?
}
}

#[cfg(target_os = "linux")]
let mut ingress_addresses = vec![];

#[cfg(target_os = "linux")]
if config.receive_broadcasts {
Copy link
Owner

Choose a reason for hiding this comment

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

Minor last change, don't mandatory. Could the match addr { part and the let mut ingress_addresses = vec![]; be under the if config.receive_broadcasts { condition?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes for the match addr and it should be as the setsockopt()s are not necessary otherwise

No for the ingress_addresses as it has to land in the LocalResource later on. Do you think it would be better to use an Option<Vec<..>> here? Currently I'm using ingress_addresses.is_empty() as an indicator whether or not the option has been set. I'm not sure if it's better allocation-wise, but semantically it probably would be.

Copy link
Owner

Choose a reason for hiding this comment

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

I have no strong opinion but it makes sense!

This option mimics the Windows behavior to also receive broadcasts on
sockets bound to a specific IP.

Signed-off-by: Konrad Gräfe <kgraefe@paktolos.net>
@lemunozm
Copy link
Owner

Thanks for this @kgraefe !

@lemunozm lemunozm merged commit e6c01b8 into lemunozm:master Mar 23, 2023
@kgraefe kgraefe deleted the udp-listen-ingress-filter branch March 23, 2023 19:05
@lemunozm lemunozm mentioned this pull request Mar 24, 2023
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants