-
Notifications
You must be signed in to change notification settings - Fork 75
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
Conversation
Ok, I'll check later, but I think your PR goes with the |
I mean, with this change, a Linux user can achieve the same behavior that a Windows user, isn't it? |
yes |
I'm ok with this PR if we extend the option docs to highlight just that. |
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),
)?; |
where |
maybe I can rework that to a flag |
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. |
By the way, feel free to add your project to the README open-source list if you want. |
6df858a
to
97eb83a
Compare
Signed-off-by: Konrad Gräfe <[email protected]>
97eb83a
to
c785981
Compare
I'm done :-) |
src/adapters/udp.rs
Outdated
#[cfg(target_os = "linux")] | ||
fn accept(&self, mut accept_remote: impl FnMut(AcceptedType<'_, Self::Remote>)) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/adapters/udp.rs
Outdated
@@ -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]; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
c785981
to
1e62022
Compare
There was a problem hiding this 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.
src/adapters/udp.rs
Outdated
#[cfg(target_os = "linux")] | ||
if !self.ingress_addresses.is_empty() { | ||
self.accept_filtered(accept_remote); | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool! 🚀
src/adapters/udp.rs
Outdated
#[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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 <[email protected]>
1e62022
to
b746f33
Compare
Thanks for this @kgraefe ! |
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.