tokio-rs/tokio · error · io::Error

no addresses to send data to

Error message

no addresses to send data to

What it means

UdpSocket::send_to resolves the destination, then calls addrs.next(); on None it returns this InvalidInput error directly (no iteration, no last_err). Unlike the bind/connect variants, send_to only ever tries the first resolved address — so an empty resolution is reported with this distinct message rather than 'could not resolve to any address.'

Source

Thrown at tokio/src/net/udp.rs:1190

    /// use tokio::net::UdpSocket;
    /// use std::io;
    ///
    /// #[tokio::main]
    /// async fn main() -> io::Result<()> {
    ///     let socket = UdpSocket::bind("127.0.0.1:8080").await?;
    ///     let len = socket.send_to(b"hello world", "127.0.0.1:8081").await?;
    ///
    ///     println!("Sent {} bytes", len);
    ///
    ///     Ok(())
    /// }
    /// ```
    pub async fn send_to<A: ToSocketAddrs>(&self, buf: &[u8], addr: A) -> io::Result<usize> {
        let mut addrs = to_socket_addrs(addr).await?;

        match addrs.next() {
            Some(target) => self.send_to_addr(buf, target).await,
            None => Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "no addresses to send data to",
            )),
        }
    }

    /// Attempts to send data on the socket to a given address.
    ///
    /// Note that on multiple calls to a `poll_*` method in the send direction, only the
    /// `Waker` from the `Context` passed to the most recent call will be scheduled to
    /// receive a wakeup.
    ///
    /// # Return value
    ///
    /// The function returns:
    ///
    /// * `Poll::Pending` if the socket is not ready to write
    /// * `Poll::Ready(Ok(n))` `n` is the number of bytes sent.

View on GitHub (pinned to 625954f365)

Solutions

  1. Pre-resolve with tokio::net::lookup_host and require a non-empty result before sending.
  2. Send to a literal SocketAddr when the peer is known by IP.
  3. Treat as 'destination unavailable' and either retry after backoff or drop the datagram per policy.
  4. Cache resolved addresses for hot send loops to avoid re-resolving and to detect resolution regressions early.

Example fix

// before
let n = sock.send_to(payload, "collector.svc:8125").await?;

// after
let target = tokio::net::lookup_host("collector.svc:8125")
    .await?.next()
    .ok_or_else(|| anyhow::anyhow!("collector.svc unresolved"))?
.to_string();
let n = sock.send_to(payload, target.parse()?).await?;
Defensive patterns

Strategy: validation

Validate before calling

let target = tokio::net::lookup_host((host.as_str(), port))
    .await?.next()
    .ok_or_else(|| anyhow::anyhow!("{host} resolved to no addresses"))?;
sock.send_to(buf, target).await?

Type guard

fn is_empty_resolution(e: &io::Error) -> bool {
    e.kind() == io::ErrorKind::InvalidInput && e.raw_os_error().is_none()
}

Try / catch

match sock.send_to(buf, addr).await {
    Ok(n) => Ok(n),
    Err(e) if e.kind() == io::ErrorKind::InvalidInput && e.raw_os_error().is_none() => {
        // no destination; drop or back off per policy
        Ok(0)
    }
    Err(e) => Err(e.into()),
}

Prevention

When it happens

Trigger: Calling UdpSocket::send_to(buf, "host:port") where to_socket_addrs yields an empty iterator (no A/AAAA records). The match arm None fires immediately.

Common situations: Sending a datagram to a misspelled or unresolvable hostname; service-discovery entry with no records; transient DNS failure returning empty; sending to a name with only a CNAME chain that bottoms out.

Related errors


AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11). Data as JSON: /api/errors/c15ad4ed6037631b. Report an issue: GitHub.