[GH-ISSUE #1474] Libresport discovery not reachable on FreeBSD and maybe other BSDs #663

Closed
opened 2026-02-27 19:31:49 +03:00 by kerem · 5 comments
Owner

Originally created by @rosorio on GitHub (Mar 14, 2025).
Original GitHub issue: https://github.com/librespot-org/librespot/issues/1474

Hi,

At the beginning of this week I found an issue with latest release of spotifyd (0.4.0) not reachable on FreeBSD when the previous (0.3.5) works like a charm.

It turns out that the latest version of librespot uses dualstack to bind the discovery service on non windows OSs,
bu the way it's done doesn't work on FreeBSD.
I did some tests on C and dualstack works properly on FreeBSD.

I still investigation how the rust call doesn't work here.

Originally created by @rosorio on GitHub (Mar 14, 2025). Original GitHub issue: https://github.com/librespot-org/librespot/issues/1474 Hi, At the beginning of this week I found an [issue](https://github.com/Spotifyd/spotifyd/issues/1343) with latest release of spotifyd (0.4.0) not reachable on FreeBSD when the previous (0.3.5) works like a charm. It turns out that the latest version of librespot uses dualstack to bind the discovery service on non windows OSs, bu the way it's done doesn't work on FreeBSD. I did [some tests on C](https://github.com/rosorio/samples-in-c/blob/master/dualstack_socket/dualstack.c) and dualstack works properly on FreeBSD. I still investigation how the rust call doesn't work here.
kerem 2026-02-27 19:31:49 +03:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@kingosticks commented on GitHub (Mar 14, 2025):

Can you fix this report? It's missing all the required information. We could assume you tried the zeroconf-interface option on the latest release but it would be more helpful if you could provide that context here, along with the other details we ask for. Please assume we don't know anything about downstream projects.

<!-- gh-comment-id:2725706836 --> @kingosticks commented on GitHub (Mar 14, 2025): Can you fix this report? It's missing all the required information. We could assume you tried the `zeroconf-interface` option on the latest release but it would be more helpful if you could provide that context here, along with the other details we ask for. Please assume we don't know anything about downstream projects.
Author
Owner

@kingosticks commented on GitHub (Mar 14, 2025):

Having read your linked issue and made some assumptions:

I've no experience using FreeBSD, but isn't this explained at https://man.freebsd.org/cgi/man.cgi?query=inet6 ?

IPV6CTL_V6ONLY (ip6.v6only) Boolean: enable/disable the pro-
hibited use of IPv4 mapped address on AF_INET6
sockets. Defaults to on.

and

Interaction between IPv4/v6 sockets
By default, FreeBSD does not route IPv4 traffic to AF_INET6 sockets.
The default behavior intentionally violates RFC2553 for security rea-
sons. Listen to two sockets if you want to accept both IPv4 and IPv6
traffic. IPv4 traffic may be routed with certain per-socket/per-node
configuration, however, it is not recommended to do so. Consult ip6(4)
for details.

It seems the system-wide default for FreeBSD is the same as Windows. FreeBSD have decided they don't like the way wildcard binds were mandated to work by the standards committee. They decided to do it differently. They suggest using two sockets instead of a single dual-stack socket, or perform explicit per-socket configuration. But as you pointed out (and here), Rust's std::net doesn't have a way to (un)set the IPV6_V6ONLY socket option like you did in C. You have to use lower level unsafe code to do that. I think we'd rather avoid that.

I suggest the workaround is to add freebsd to the existing special case for Windows. And/or have DiscoveryServer use the zeroconf_ip option (currently only used by some mdns backends) so people can control this themselves. It is weird that we allow controlling the port but not the interface here.

<!-- gh-comment-id:2725832367 --> @kingosticks commented on GitHub (Mar 14, 2025): Having read your linked issue and made some assumptions: I've no experience using FreeBSD, but isn't this explained at https://man.freebsd.org/cgi/man.cgi?query=inet6 ? > IPV6CTL_V6ONLY (ip6.v6only) Boolean: enable/disable the pro- hibited use of IPv4 mapped address on AF_INET6 sockets. Defaults to on. and > Interaction between IPv4/v6 sockets By default, FreeBSD does not route IPv4 traffic to AF_INET6 sockets. The default behavior intentionally violates RFC2553 for security rea- sons. Listen to two sockets if you want to accept both IPv4 and IPv6 traffic. IPv4 traffic may be routed with certain per-socket/per-node configuration, however, it is not recommended to do so. Consult [ip6(4)](https://man.freebsd.org/cgi/man.cgi?query=ip6&sektion=4&apropos=0&manpath=FreeBSD+14.2-RELEASE+and+Ports) for details. It seems the system-wide *default* for FreeBSD is the same as Windows. FreeBSD have decided they don't like the way wildcard binds were mandated to work by the standards committee. They decided to do it differently. They suggest using two sockets instead of a single dual-stack socket, or perform explicit per-socket configuration. But as you pointed out (and [here](https://github.com/rust-lang/rust/issues/130668)), Rust's std::net doesn't have a way to (un)set the `IPV6_V6ONLY` socket option like you did in C. You have to use lower level unsafe code to do that. I think we'd rather avoid that. I suggest the workaround is to add freebsd to the existing special case for Windows. And/or have `DiscoveryServer` use the `zeroconf_ip` option (currently only used by some mdns backends) so people can control this themselves. It is weird that we allow controlling the port but not the interface here.
Author
Owner

@rosorio commented on GitHub (Mar 14, 2025):

In fact it is possible to have the same behavior in rust by explicitly enabling the IPV6_V6ONLY flag using socket2. The following code works on FreeBSD creating a dualstack socket.

use socket2::{Socket, Domain, Type, Protocol};
use std::net::{SocketAddr, TcpListener};
use std::io::{Read, Write};

fn main() -> std::io::Result<()> {
    // Create an IPv6 socket
    let socket = Socket::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP))?;
use socket2::{Socket, Domain, Type, Protocol};
use std::net::{SocketAddr, TcpListener};
use std::io::{Read, Write};

fn main() -> std::io::Result<()> {
    // Create an IPv6 socket
    let socket = Socket::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP))?;

    // Allow dual-stack by setting IPV6_V6ONLY to false (0)
    socket.set_only_v6(false)?;

    // Bind to an address
    let addr: SocketAddr = "[::]:8080".parse().unwrap();
    socket.bind(&addr.into())?;
    socket.listen(128)?;

    // Convert socket into TcpListener
    let listener: TcpListener = socket.into();

    println!("Server listening on [::]:8080 (dual-stack enabled)");

    for stream in listener.incoming() {
        match stream {
            Ok(mut stream) => {
                let peer_addr = stream.peer_addr()?;
                println!("Client connected: {}", peer_addr);

                // Echo back data
                let mut buffer = [0; 1024];
                let bytes_read = stream.read(&mut buffer)?;
                stream.write_all(&buffer[..bytes_read])?;
            }
            Err(e) => eprintln!("Connection failed: {}", e),
        }
    }
    Ok(())
}

I will see if it's feasible to patch librespot this way.
On the other side assuming ipv4 only is the easy way :)

Thanks for your help

<!-- gh-comment-id:2725891084 --> @rosorio commented on GitHub (Mar 14, 2025): In fact it is possible to have the same behavior in rust by explicitly enabling the IPV6_V6ONLY flag using socket2. The following code works on FreeBSD creating a dualstack socket. ``` use socket2::{Socket, Domain, Type, Protocol}; use std::net::{SocketAddr, TcpListener}; use std::io::{Read, Write}; fn main() -> std::io::Result<()> { // Create an IPv6 socket let socket = Socket::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP))?; use socket2::{Socket, Domain, Type, Protocol}; use std::net::{SocketAddr, TcpListener}; use std::io::{Read, Write}; fn main() -> std::io::Result<()> { // Create an IPv6 socket let socket = Socket::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP))?; // Allow dual-stack by setting IPV6_V6ONLY to false (0) socket.set_only_v6(false)?; // Bind to an address let addr: SocketAddr = "[::]:8080".parse().unwrap(); socket.bind(&addr.into())?; socket.listen(128)?; // Convert socket into TcpListener let listener: TcpListener = socket.into(); println!("Server listening on [::]:8080 (dual-stack enabled)"); for stream in listener.incoming() { match stream { Ok(mut stream) => { let peer_addr = stream.peer_addr()?; println!("Client connected: {}", peer_addr); // Echo back data let mut buffer = [0; 1024]; let bytes_read = stream.read(&mut buffer)?; stream.write_all(&buffer[..bytes_read])?; } Err(e) => eprintln!("Connection failed: {}", e), } } Ok(()) } ``` I will see if it's feasible to patch librespot this way. On the other side assuming ipv4 only is the easy way :) Thanks for your help
Author
Owner

@kingosticks commented on GitHub (Mar 14, 2025):

Yes, nobody said it wasn't possible. socket2 is a higher-level library around unsafe code to set the socket option. Annoying to have to pull in another dependency to sort this, but maybe that is preferable to messing around with what each BSD variant decided was best.

Indeed, we don't need dual-stack, we don't really care. We only want it to Just Work for the user. But just assuming IPv4 seems a bit sad/lazy in 2025.

<!-- gh-comment-id:2725905736 --> @kingosticks commented on GitHub (Mar 14, 2025): Yes, nobody said it wasn't possible. `socket2` is a higher-level library around unsafe code to set the socket option. Annoying to have to pull in another dependency to sort this, but maybe that is preferable to messing around with what each BSD variant decided was best. Indeed, we don't need dual-stack, we don't really care. We only want it to Just Work for the user. But just assuming IPv4 seems a bit sad/lazy in 2025.
Author
Owner

@rosorio commented on GitHub (Mar 14, 2025):

agree, since it's not librespot responsibility , we can close this issue.

<!-- gh-comment-id:2725932044 --> @rosorio commented on GitHub (Mar 14, 2025): agree, since it's not librespot responsibility , we can close this issue.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/librespot#663
No description provided.