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

feat(net): added reuseAddress and reusePort in Sockets API #13794

Closed
wants to merge 5 commits into from
Closed

feat(net): added reuseAddress and reusePort in Sockets API #13794

wants to merge 5 commits into from

Conversation

Trolloldem
Copy link
Contributor

Support for SO_REUSEADDR socket option is requested in #1195, to avoid using different ports during tests. In order to achieve the wanted feature, it now possible to use the SO_REUSEPORT socket option in addition.

The implemented changes are:

  1. 2 additional optional arguments for Deno.listen and Deno.listenTls, namely reuseAddr and reusePort (one for each supported option)
  2. changed the socket2 entry to support the SO_REUSEPORT option
  3. proposed a test to verify that the reusePort option works as intended

In this moment, the option is checked and set only for op_net_listen and op_tls_listen. In particular, for op_net_listen, changes are applied only to the listen_tcp method.

After testing reuseAddr, it has been noted that the tokio::net::lookup_host function use to solve the provided hostname, sometimes resolves the address 0.0.0.0 to 127.0.0.1, leading to unwanted effects when the following code is used:

const listener1 = Deno.listen({ hostname: "127.0.0.1", port: 3500, reuseAddress: true});
const listener2 = Deno.listen({ hostname: "0.0.0.0", port: 3500, reuseAddress: true});

One possible solution could be using the dedicated dns-lookup package

@CLAassistant
Copy link

CLAassistant commented Feb 28, 2022

CLA assistant check
All committers have signed the CLA.

Comment on lines +1090 to +1102
let socket = Socket::new(domain, Type::STREAM, None)?;
if reuse_address.is_some() {
socket.set_reuse_address(reuse_address.unwrap())?;
} else {
socket.set_reuse_address(true)?;
}
if reuse_port.is_some() {
socket.set_reuse_port(reuse_port.unwrap())?;
}
let socket_addr = socket2::SockAddr::from(bind_addr);
socket.bind(&socket_addr)?;
socket.listen(128)?;
socket.set_nonblocking(true)?;
Copy link
Member

Choose a reason for hiding this comment

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

I believe these are only available for unix systems which causes CI failure

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is the case. I noticed that SO_REUSEADDR in windows seems to achieve the same results of SO_REUSEPORT in Linux. I will add a #[cfg(unix)] to address this issue.
I also noticed that once swapped to Socket2 for the creation of TcpListener, I had to set SO_REUSEADDR to true by default in order to pass the net_test unit tests.

Copy link
Member

Choose a reason for hiding this comment

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

@Trolloldem that's a bit concerning. May I suggest to open a new PR that first switches to socket2 crate and then add new APIs in follow up?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bartlomieju this is ok to me. But I think this is due to the different way the std::net treats the presence of
a socket in the TIME_WAIT state compared to socket2. During the test I used the netstat utility to investigate why these test failures appened, and I noticed that the socket previously closed by other test were in the TIME_WAIT state, thus not allowing other sockets to bind to their hostname-port pair.

Copy link
Member

Choose a reason for hiding this comment

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

That makes sense, that's why I'm suggesting to split the PR into two - so we can unsure that when changing the crates we keep the same behavior

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I will open a new PR with only the changes needed for socket2. When it is ready I will close this one, and propose the changes for SO_REUSEADDR in a new one

Copy link
Contributor Author

@Trolloldem Trolloldem Mar 1, 2022

Choose a reason for hiding this comment

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

Before starting to work on the new PR, I wanted to check how the current std::net::TcpListener is configured. I used the following code:

fn listen_tcp(
  state: &mut OpState,
  addr: SocketAddr,
) -> Result<(u32, SocketAddr), AnyError> {
  let std_listener1 = std::net::TcpListener::bind(&addr)?;
  let so: socket2::Socket = socket2::Socket::from(std_listener1);
  if so.reuse_address()? {
    panic!("The reuseADDR is: {}", so.reuse_address()?);
  }
  let std_listener: std::net::TcpListener = so.into();
  std_listener.set_nonblocking(true)?;
  let listener = TcpListener::from_std(std_listener)?;
  let local_addr = listener.local_addr()?;
  let listener_resource = TcpListenerResource {
    listener: AsyncRefCell::new(listener),
    cancel: Default::default(),
  };
  let rid = state.resource_table.add(listener_resource);

  Ok((rid, local_addr))
}

And the I run:
target/debug/deno test --allow-all --unstable --location=http://js-unit-tests/foo/bar cli/tests/unit/net_test.ts
The obtained output is the following:

running 33 tests from file:///home/oldem/git/deno/cli/tests/unit/net_test.ts
test netTcpListenClose ...
============================================================
Deno has panicked. This is a bug in Deno. Please report this
at https://github.com/denoland/deno/issues/new.
If you can reliably reproduce this panic, include the
reproduction steps and re-run with the RUST_BACKTRACE=1 env
var set and include the backtrace in your report.

Platform: linux x86_64
Version: 1.19.1
Args: ["target/debug/deno", "test", "--allow-all", "--unstable", "--location=http://js-unit-tests/foo/bar", "cli/tests/unit/net_test.ts"]

thread '<unnamed>' panicked at 'The reuseADDR is: true', ext/net/ops.rs:434:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The panic does not happen when the check over so.reuse_address() is not present.
So, at least on Linux, it does seem that in this moment the socket already has SO_REUSEADDR option configured.
It is still preferable to separate the two PR?

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.

3 participants