The client dials the first IPv4 address alone, as 1.12.1 does (#1385)
**Problem.** `localhost` resolves to `::1` first. benilla dialled every resolved address in order, so a realmd bound only to IPv4 left a Windows login waiting about 2 s on `::1`. **Reference.** 1.12.1 resolves the realmlist host to one IPv4 address and dials it alone (`WowConnection::ConnectToHost` `0x5b6e40`): `inet_addr` (WSOCK32 ordinal 10), else the first entry of `gethostbyname` (ordinal 52). It never dials IPv6. **Change.** The dial takes the resolver's first IPv4 address and nothing else. A host with no IPv4 address fails as unresolved. **Tests.** `localhost` dials `127.0.0.1`. The first IPv4 address of several is dialled. A host that resolves only to IPv6 gives no target. Smoke: green. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Sam <samuv.eth@gmail.com> (cherry picked from commit 8c3587479059423dc69e67a345fc933a9ba40b17)
This commit is contained in:
@@ -23,7 +23,7 @@ pub use world::{
|
||||
WardenRequired, WorldAuthReject, WorldReader, WorldSession, WorldWriter, WORLD_PORT,
|
||||
};
|
||||
|
||||
use std::net::TcpStream;
|
||||
use std::net::{SocketAddr, TcpStream};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use benilla_srp::{NormalizedString, PublicKey, SrpClientChallenge, SESSION_KEY_LENGTH};
|
||||
@@ -81,12 +81,11 @@ impl std::fmt::Display for DialFailure {
|
||||
|
||||
impl std::error::Error for DialFailure {}
|
||||
|
||||
/// Resolves, then tries every resolved address in turn: `localhost` gives `::1` first, and a
|
||||
/// server may bind only IPv4.
|
||||
/// Resolves, then dials the first IPv4 address ([`first_ipv4`]).
|
||||
fn dial(host: &str, port: u16) -> Result<TcpStream> {
|
||||
use std::net::ToSocketAddrs;
|
||||
let address = format!("{host}:{port}");
|
||||
let addrs: Vec<std::net::SocketAddr> = match (host, port).to_socket_addrs() {
|
||||
let addrs: Vec<SocketAddr> = match (host, port).to_socket_addrs() {
|
||||
Ok(addrs) => addrs.collect(),
|
||||
Err(e) => {
|
||||
return Err(anyhow::Error::new(DialFailure {
|
||||
@@ -96,13 +95,13 @@ fn dial(host: &str, port: u16) -> Result<TcpStream> {
|
||||
.context(format!("resolving {host}: {e}")))
|
||||
}
|
||||
};
|
||||
if addrs.is_empty() {
|
||||
let Some(target) = first_ipv4(&addrs) else {
|
||||
return Err(anyhow::Error::new(DialFailure {
|
||||
address,
|
||||
unresolved: true,
|
||||
}));
|
||||
}
|
||||
TcpStream::connect(&addrs[..]).map_err(|e| {
|
||||
};
|
||||
TcpStream::connect(target).map_err(|e| {
|
||||
anyhow::Error::new(DialFailure {
|
||||
address,
|
||||
unresolved: false,
|
||||
@@ -111,6 +110,13 @@ fn dial(host: &str, port: u16) -> Result<TcpStream> {
|
||||
})
|
||||
}
|
||||
|
||||
/// The address 1.12.1 dials: IPv4 alone, the first the resolver gives (`WowConnection::ConnectToHost`
|
||||
/// `0x5b6e40`: `inet_addr`, else `gethostbyname`'s first entry). `localhost` resolves to `::1`
|
||||
/// first, which a realmd bound to IPv4 refuses only after about 2 s on Windows.
|
||||
fn first_ipv4(addrs: &[SocketAddr]) -> Option<SocketAddr> {
|
||||
addrs.iter().copied().find(SocketAddr::is_ipv4)
|
||||
}
|
||||
|
||||
/// A realm from realmd's realm list, with every field the wire carries.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RealmInfo {
|
||||
@@ -269,3 +275,26 @@ mod host_port_tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod dial_tests {
|
||||
use super::first_ipv4;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
fn addrs(list: &[&str]) -> Vec<SocketAddr> {
|
||||
list.iter().map(|a| a.parse().unwrap()).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn localhost_dials_its_ipv4_address() {
|
||||
let got = first_ipv4(&addrs(&["[::1]:3724", "127.0.0.1:3724"]));
|
||||
assert_eq!(got, Some("127.0.0.1:3724".parse().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_first_ipv4_address_is_dialled_and_ipv6_never() {
|
||||
let got = first_ipv4(&addrs(&["[2001:db8::2]:1", "10.0.0.2:1", "10.0.0.1:1"]));
|
||||
assert_eq!(got, Some("10.0.0.2:1".parse().unwrap()));
|
||||
assert_eq!(first_ipv4(&addrs(&["[2001:db8::1]:1"])), None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user