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

Unicast Send/Receive Support #65

Open
pixsperdavid opened this issue Sep 8, 2022 · 19 comments
Open

Unicast Send/Receive Support #65

pixsperdavid opened this issue Sep 8, 2022 · 19 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@pixsperdavid
Copy link
Contributor

I need unicast send/receive support for my current project so willing to work on adding this feature. Similar to #61, could you give an overview of tasks that are involved in implementing this?

@keepsimple1
Copy link
Owner

keepsimple1 commented Sep 9, 2022

Thanks for stepping up and take the initiative! Here is a list of tasks on my mind:

  • sockets for receiving: currently Zeroconf::intf_socks only processes multicast packets, these socks could also process incoming unicast packets received. To me, this would need some experiments as all intf_socks are bound to 0.0.0.0, hence they are probably load-balanced by the kernel for unicast. How can we tell the unicast packets from the multicast packets?

We probably are okay, because:

  1. load-balanced: UDP is unordered anyway. As long as we receive the packet, should be okay.
  2. distinguish unicast packets from multicast packet: probably not required, as long as we process them. We can figure out unicast by checking the domain in the service type name.

another option is to use a different server port (i.e. not 5353) just for unicast. But it's tricky to pick a port number. It will be worth to check out what existing unicast DNS-SD software do.

  • socket for sending: this is probably easier, just adding a new socket in Zeroconf to send all unicast outgoing packets. The bind port can be chosen by the system.

  • handle unicast domains in browse: for unicast, the domain will be different from .local. , and similar to a regular domain like cs.mycompany.com.. When detected non-local domain, we should use unicast socket to send out the query. We might also need to do a regular DNS lookup for the domain IP address (might be able to use std::net::ToSocketAddrs).

The above is the main points I can think of for now. There are muddy areas in unicast DNS-SD I don't know enough about. Hope we can figure everything out along the way.

@pixsperdavid
Copy link
Contributor Author

Thanks for the information, I'll make a start and submit a draft PR for review when ready.

@pixsperdavid
Copy link
Contributor Author

distinguish unicast packets from multicast packet: probably not required, as long as we process them.

Unfortunately, according to RFC 6702 5.5, this is required. The library should respond to any queries sent to the server's unicast address with unicast responses. Therefore the library needs to determine the destination address of the incoming packets. I did some research and there's not currently an easy way to get this information in a cross-platform way in Rust.

The best example I've found so far is the multicast socket crate. So a potential way forward could be to base the implementation on this, although it would require separate implementations for windows and unix sockets, and would also require some unsafe code. Thoughts?

@keepsimple1
Copy link
Owner

keepsimple1 commented Sep 11, 2022

The best example I've found so far is the multicast socket crate. So a potential way forward could be to base the implementation on this, although it would require separate implementations for windows and unix sockets, and would also require some unsafe code. Thoughts

I think it looks like a good way to go. Particularly we can use IP_PKTINFO option similar to multicast socket crate does here. But instead of using nix crate to set socket option, I think we can try to use libc directly (for UNIX platforms) to avoid a dependency on nix crate.

And instead of adding it directly into mdns-sd, what if we implement it as a separate small crate that only depends on libc and winapi ? That crate is basically an extension of socket2, where it supports additional socket options by operating on as_raw_fd (unix) and as_raw_socket (windows), and also related API like recvmsg. This crate would require some unsafe code like you pointed out.

Then mdns-sd would use this small crate to receive UDP packets where destination address (and probably source address) info included, using a safe, cross-platform API. Would that make sense?

@lu-zero
Copy link
Contributor

lu-zero commented Nov 10, 2022

What prevents us from using the nix crate though? winapi is the windows equivalent to nix in the end ^^;

@keepsimple1
Copy link
Owner

What prevents us from using the nix crate though? winapi is the windows equivalent to nix in the end ^^;

In this crate, we moved away from nix to socket2 to take advantage of its cross-platform support. For the same reason we are not using winapi directly.

For the separate small crate I was suggesting above, yes we could use nix (and winapi), even though I was suggesting libc as it would be a smaller dependency.

@keepsimple1 keepsimple1 added the enhancement New feature or request label Jan 12, 2023
@keepsimple1 keepsimple1 added the help wanted Extra attention is needed label Oct 7, 2023
@dalepsmith
Copy link
Contributor

Here is a list of tasks on my mind:

2. distinguish unicast packets from multicast packet: probably not required, as long as we process them. We can figure out unicast by checking the domain in the service type name.

I think looking at the destination address could tell if it's unicast or multicast. The multicast packets be sent to the multicast address, but unicast to the host address. Unless unicast is also sent to the multicast address?

@keepsimple1
Copy link
Owner

keepsimple1 commented Feb 29, 2024

I think looking at the destination address could tell if it's unicast or multicast. The multicast packets be sent to the multicast address, but unicast to the host address. Unless unicast is also sent to the multicast address?

Yes looking at the destination address can tell if it's unicast or multicast.

The problem is that it's not easy to determine the destination address (AFAIK). We know which interface (and its local address) received the packet, but the local address is not necessarily same as the dest. address.

As in earlier comments, one possibility is to use socket option IP_PKTINFO (search in the page) to get the destination address via recvmsg method. But socket2 does not support it yet. There is a PR opened for that but not seem to be merged anytime soon.

I'm open to use other approaches or other crate if can solve the problem and not too big dependencies.

@pixsperdavid
Copy link
Contributor Author

So, have finally had some available time to complete phase one, I've just published a crate (https://crates.io/crates/socket-pktinfo) which provides a cross-platform way to consume the IP_PKTINFO. I will now work on a PR to integrate this into mdns-sd.

@keepsimple1
Copy link
Owner

It's awesome you created the socket-pktinfo crate! Nice work! I'd love to use it to solve this issue, but I do have some other thinking going on:

Recently after discussing issue #179 and got inspired by it, I've been thinking a different approach to solve this issue:

  1. Change the current multicast sockets to bind to mDNS multicast address only (instead of INADDR_ANY). The idea is that such sockets will not receive any unicast packets.

  2. Create separate sockets only for unicast send/recv. They don't join the mDNS multicast group.

I haven't get around to implement / verify this idea yet, but conceptually it might just work (I think). If that could work, it would relatively small change and no new dependency. If it doesn't work, I think we should use your crate. Let me know what you think, especially if any obvious errors in my thinking. Cheers!

@dalepsmith
Copy link
Contributor

I know you can have several processes listening on the same multicast address:port.
I'm not sure if it's also possible listen to unicast on that same port. Might need to use the SO_REUSEPORT socket option?

@pixsperdavid
Copy link
Contributor Author

pixsperdavid commented Mar 20, 2024

Change the current multicast sockets to bind to mDNS multicast address only (instead of INADDR_ANY). The idea is that such sockets will not receive any unicast packets.

AFAIK binding to a multicast socket address directly is unsupported on Windows. I tested the following code:

let multicast_socket_addr: SockAddr = SocketAddr::new(Ipv4Addr::new(238, 10, 20, 31).into(), 8000).into();
let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;
socket.bind(&multicast_socket_addr)?;

This is successful on Linux but on Windows returns Error: Os { code: 10049, kind: AddrNotAvailable, message: "The requested address is not valid in its context." }

Even if it were possible to do this on Windows, the other issue I see with this approach is that it delegates the selection of network interface to the operating system. IMO This isn't a desirable outcome as it prevents consumers of the library from selecting the network interface.

@dalepsmith
Copy link
Contributor

dalepsmith commented Mar 20, 2024

I had a similar problem in Linux years ago. I added the multicast subnet to the interface to get around it. (I guess I should have been using INADDR_ANY)

Might be worth trying, but then it would require users to do that. And it might not even be possible with some windows versions (Home vs Pro or whatever, I'm not a windows guy)

@pixsperdavid
Copy link
Contributor Author

pixsperdavid commented Mar 20, 2024

OK, actually looking at the ServiceDaemon::new_socket_bind function now, I'm slightly surprised that every socket is created bound to INADDR_ANY and then the interface is selected by calling set_multicast_if_v4. My present understanding (which I should verify) is that you can control selection of multicast network interface by binding the socket to the local IP of the interface, and that you would only need to use set_multicast_if_v4 if you were binding to INADDR_ANY. Was this approach ever attempted and if so did it not function as expected?

@keepsimple1
Copy link
Owner

keepsimple1 commented Mar 20, 2024

My present understanding (which I should verify) is that you can control selection of multicast network interface by binding the socket to the local IP of the interface, and that you would only need to use set_multicast_if_v4 if you were binding to INADDR_ANY. Was this approach ever attempted and if so did it not function as expected?

I cannot find my logs but my memory is that I tried and it didn't work. The current implementation is the best method I could find actually working (except for unicast).

That said, I opened a PR #187 to try out unicast sockets. And checkout what I mentioned in the later part of the PR, i.e. a major problem is that we don't know the source IP of the packets. (Currently it's ok as we don't need to unicast the host). This problem persists regardless how we bind the sockets. (Is that correct understanding about your use case?)

Without using socket2 unsafe .recv_from(), it seems that our best option is to use your new crate to find out the source IP?

@keepsimple1
Copy link
Owner

Might need to use the SO_REUSEPORT socket option?

yes we're using this option (when supported, otherwise SO_REUSEADDR).

@keepsimple1
Copy link
Owner

@pixsperdavid I think my experiment PR #187 showed the same thing as you mentioned in your earlier comment, i.e. binding multicast address does not always work. Plus I think we also have a problem of finding the source IP to support unicast use cases.

So I think our best option is to use your crate socket-pktinfo, and if you'd like working on a PR to integrate into mdns-sd, that'll be awesome!

@lu-zero
Copy link
Contributor

lu-zero commented Mar 23, 2024

I guess if rust-lang/socket2#313 gets eventually solved things might get simpler. (adding this issue to the list so we might get notified once there is progress in that regard)

@pixsperdavid
Copy link
Contributor Author

See #190 for draft

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

4 participants