If we run the following code:
use std::net::;
fn () {
{
let = ::(":::8000").();
!("Bound to: {}", .().());
}
{
let = ::("[::]:8000").();
!("Bound to: {}", .().());
}
}
We get the following output:
Bound to: [::]:8000
Bound to: [::]:8000
Therefore, it’s reasonable to believe that both calls run the same code path, and therefore will always work the same way. However, that’s not the case!
Breaking It Down
If we look at the signature for TcpListener::bind
:
pub fn <: ToSocketAddrs>(: ) -> <TcpListener>;
We see that it takes a type implementing ToSocketAddrs
:
pub trait {
type : < = SocketAddr>;
// Required method
fn (&) -> <::>;
}
For strings, the documentation states that
the string should be either a string representation of a
SocketAddr
as expected by itsFromStr
implementation or a string like<host_
pair wherename>:<port> <port>
is a u16 value.
and looking at the source, we can see the following:
// accepts strings like 'localhost:12345'
#[(feature = "rust1", since = "1.0.0")]
impl ToSocketAddrs for {
type = vec::IntoIter<SocketAddr>;
fn (&) -> io::Result<vec::IntoIter<SocketAddr>> {
// try to parse as a regular SocketAddr first
if let () = .() {
return (![].());
}
resolve_socket_addr(.()?)
}
}
The implementation
-
Checks if it can parse the string as a
SocketAddr
, and if so it returns it directly. -
Otherwise, it tries to resolve the address using platform interfaces, which on Linux is
getaddrinfo(3)
.
We can verify that :::8000
cannot be parsed as a SocketAddr
like so:
use std::net::;
fn () {
{
let = ":::8000";
!("{:?}", .::<>());
}
{
let = "[::]:8000";
!("{:?}", .::<>());
}
}
which prints
Err(AddrParseError(Socket))
Ok([::]:8000)
We can also verify that getaddrinfo
is called on :::8000
using uftrace:
use std::net:: as _;
fn () {
let = ":::8000";
!("{:?}", .());
}
$ cargo build && uftrace --no-pager -la ./target/debug/testing
Ok(IntoIter([[::]:8000]))
# DURATION TID FUNCTION
3.379 us [ 20737] | poll(0x7ffc03010170, 3, 0) = 0;
0.249 us [ 20737] | signal(SIGPIPE, 0x1) = 0;
0.347 us [ 20737] | sysconf();
0.055 us [ 20737] | pthread_self();
84.702 us [ 20737] | pthread_getattr_np();
0.040 us [ 20737] | pthread_attr_getstack();
0.062 us [ 20737] | pthread_attr_destroy();
0.238 us [ 20737] | sigaction(SIGSEGV, 0, 0x7ffc03010170) = 0;
0.142 us [ 20737] | sigaction(SIGBUS, 0, 0x7ffc03010170) = 0;
0.594 us [ 20737] | sigaltstack();
0.058 us [ 20737] | getauxval();
1.477 us [ 20737] | mmap64(0, 12288, PROT_WRITE|PROT_READ, MAP_STACK|MAP_ANON|MAP_PRIVATE, -1, 0) = 0x76e3984dd000;
1.351 us [ 20737] | mprotect(0x76e3984dd000, 4096, PROT_NONE) = 0;
0.352 us [ 20737] | sigaltstack();
0.261 us [ 20737] | sigaction(SIGBUS, 0x7ffc03010170, 0) = 0;
0.158 us [ 20737] | pthread_key_create();
0.052 us [ 20737] | pthread_setspecific();
0.101 us [ 20737] | bcmp();
0.183 us [ 20737] | memcpy(0x7ffc0300fd68, 0x5f4accdaec9e, 2);
57.689 us [ 20737] | getaddrinfo("::", "NULL", 0x7ffc0300fc80, 0x7ffc0300fd28) = 0;
0.120 us [ 20737] | malloc(128) = 0x5f4ade24aad0;
0.192 us [ 20737] | freeaddrinfo(0x5f4ade2853b0);
0.081 us [ 20737] | malloc(1024) = 0x5f4ade1a6290;
0.097 us [ 20737] | memcpy(0x5f4ade1a6290, 0x5f4accdaea72, 2);
0.064 us [ 20737] | memcpy(0x5f4ade1a6292, 0x5f4accdb3182, 1);
0.057 us [ 20737] | memcpy(0x5f4ade1a6293, 0x5f4accdae678, 8);
0.052 us [ 20737] | memcpy(0x5f4ade1a629b, 0x5f4accdb3182, 1);
0.046 us [ 20737] | memcpy(0x5f4ade1a629c, 0x5f4accdb306c, 1);
0.051 us [ 20737] | memcpy(0x5f4ade1a629d, 0x5f4accdb306c, 1);
0.050 us [ 20737] | memcpy(0x5f4ade1a629e, 0x5f4accdb3028, 2);
0.069 us [ 20737] | memcpy(0x5f4ade1a62a0, 0x5f4accdb306d, 2);
0.059 us [ 20737] | memcpy(0x5f4ade1a62a2, 0x7ffc0300f88c, 4);
0.054 us [ 20737] | memcpy(0x5f4ade1a62a6, 0x5f4accdb3187, 1);
0.055 us [ 20737] | memcpy(0x5f4ade1a62a7, 0x5f4accdb2f72, 1);
0.048 us [ 20737] | memcpy(0x5f4ade1a62a8, 0x5f4accdb2f72, 1);
0.045 us [ 20737] | memcpy(0x5f4ade1a62a9, 0x5f4accdaeca5, 1);
4.425 us [ 20737] | write(1, 0x5f4ade1a6290, 26) = 26;
0.061 us [ 20737] | memcpy(0x5f4ade1a6290, 0x5f4accdaeca6, 0);
0.096 us [ 20737] | free(0x5f4ade24aad0);
0.061 us [ 20737] | free(0x5f4ade1a6290);
0.050 us [ 20737] | getauxval();
0.127 us [ 20737] | sigaltstack();
1.858 us [ 20737] | munmap(0x76e3984dd000, 12288) = 0;
0.050 us [ 20737] | pthread_self();
Playing Around
We can also do a sneaky by patching getaddrinfo
to filter certain addresses.
Using the libc
crate for convenience, let’s make a LD_
able library!
- Cargo.toml
[package]
name = "testing"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
libc = "0.2.169"
- src/lib.rs
use libc::{, , , , , };
#[]
unsafe extern "C" fn (
: *const ,
: *const ,
: *const ,
: *mut *mut ,
) -> {
// 1. Get the real `getaddrinfo`
let = libc::(libc::, c"getaddrinfo".());
!(!.());
// 2. Call the real `getaddrinfo` and return on error
let mut : *mut = std::ptr::();
let : fn(
*const ,
*const ,
*const ,
*mut *mut ,
) -> = std::mem::();
let = (, , , &raw mut );
if != 0 {
return ;
}
let = std::env::("SKIP_IPV4").(|| == "1");
let = std::env::("SKIP_IPV6").(|| == "1");
// 3. Filter the returned `addrinfo` structs
let mut = &raw mut ;
let mut : *mut = ;
while !.() {
let = (*).;
if ( == && ) || ( == && ) {
* = (*).;
let : *mut = ;
= (*).;
(*). = std::ptr::();
();
continue;
}
= &raw mut (*).;
= (*).;
}
* = ;
}
With the same example as before:
$ LD_PRELOAD=./target/debug/libtesting.so ./target/debug/testing
Ok(IntoIter([[::]:8000]))
$ SKIP_IPV6=1 LD_PRELOAD=./target/debug/libtesting.so ./target/debug/testing
Ok(IntoIter([]))
Success!
Conclusion
Is this relevant? Who knows! I’d like to hear if you know of any scenario where this makes significant difference…