If we run the following code:
use ::::;
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
SocketAddras expected by itsFromStrimplementation or a string like<host_name>:<port>pair where<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 ::::;
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 :::: 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_PRELOADable library!
[package]
name = "testing"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
libc = "0.2.169"
use ::{, , , , , };
#[]
unsafe extern "C" fn (
: *const ,
: *const ,
: *const ,
: *mut *mut ,
) -> {
// 1. Get the real `getaddrinfo`
let = ::(::, c"getaddrinfo".());
!(!.());
// 2. Call the real `getaddrinfo` and return on error
let mut : *mut = ::::();
let : fn(
*const ,
*const ,
*const ,
*mut *mut ,
) -> = ::::();
let = (, , , &raw mut );
if != 0 {
return ;
}
let = ::::("SKIP_IPV4").(|| == "1");
let = ::::("SKIP_IPV6").(|| == "1");
// 3. Filter the returned `addrinfo` structs
let mut = &raw mut ;
let mut : *mut = ;
while !.() {
let = (*).;
if ( == && ) || ( == && ) {
* = (*).;
let : *mut = ;
= (*).;
(*). = ::::();
();
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...