Search code examples
rustipv6hyper

hyper client cannot lookup address information for server running on IPv6 localhost


I have a simple HTTP server using Router and Iron on port 3005. It is not doing anything exciting. I believe it just echoes back the request, but the details are not important.

I have also made a simple client using hyper's client module to send requests to the server.

Whenever I run the server on IPv4 localhost, I experience no issues. I can query it both with my client and with curl. If I start the server on my IPv6 localhost (I am using the shortened version ::1), I am only able to access the server with curl.

This indicates that the server is running properly and responding, but my hyper Client code to access it fails, reporting:

Err(Io(Error { repr: Custom(Custom { kind: Other, error: StringError("failed to lookup address information: Name or service not known") }) })) thread 'main' panicked at 'called Result::unwrap() on an Err value: Io(Error { repr: Custom(Custom { kind: Other, error: StringError("failed to lookup address information: Name or service not known") }) })', /checkout/src/libcore/result.rs:860

The code I use to send the POST request is as follows:

let addr = "http://[::1]:3005/message";
let mut res = self.client.post(addr).body(s.as_str()).send().unwrap();

Where s is some payload I am sending.

I have tried the expanded IPv6 address as well ([0:0:0:0:0:0:0:1]) and I get the same error.

I have also tried both the shortened and the expanded IPv6 addresses without the brackets. I get "invalid port -" with the expanded address and "Empty Host" with the shortened.

To reproduce this behaviour, you can use these small examples (uncomment the commented lines to recieve the error):

Server

extern crate iron;

use iron::prelude::*;
use iron::status;

fn hello_world(_: &mut Request) -> IronResult<Response> {
    println!("Recvd a request");
    Ok(Response::with((status::Ok, "Hello World!")))
}

fn main() {
    let port = 3000;
    //let addr = format!("{}:{}", "[::1]", port);
    let addr = format!("{}:{}", "localhost", port);

    println!("Server opened on {}", addr);

    Iron::new(hello_world).http(addr).unwrap();
}

Client

// hyper 0.10.13
extern crate hyper;

use hyper::*;
use std::io::Read;

fn main() {
    let client = Client::new();
    //let mut res = client.get("http://[::1]:3000/").send().unwrap();
    let mut res = client.get("http://localhost:3000/").send().unwrap();

    let mut s = String::new();
    res.read_to_string(&mut s).unwrap();

    println!("response contained: {}", s);
}

ClientV2

// For people that want to try with hyper 0.11.X
extern crate futures;
extern crate hyper;
extern crate tokio_core;

use std::io::{self, Write};
use futures::{Future, Stream};
use hyper::Client;
use tokio_core::reactor::Core;

fn main() {
    let mut core = Core::new().unwrap();
    let client = Client::new(&core.handle());

    let uri = "http://[::1]:3000/".parse().unwrap();
    let work = client.get(uri).and_then(|res| {
        println!("Response: {}", res.status());

        res.body().for_each(|chunk| {
            io::stdout()
                .write_all(&chunk)
                .map(|_| ())
                .map_err(From::from)
        })
    });

    core.run(work).unwrap();

}

Note1:

You need hyper 0.10.X in order to get this code running. In my case I was using 0.10.13

Note2:

I am sending GET requests with no payload, in order to abstract out the irrelevant bits of functionality.

Note3:

It seems like hyper 0.10.X and hyper 0.11.X handle the IPv6 server differently. Hyper 0.10.X gives the aforementioned error, while 0.11.X gives me Response Code 400 Bad Request.


Solution

  • IPv6 support seems to be an issue with the previous and current versions of hyperium/hyper (<=0.11.23)

    The developers advise using the Reqwest crate for clients using hyper 0.11.X, but since Reqwest builds upon hyper, the results will be the same.


    The solution I have found so far is to use the bindings of cURL for Rust as cURL seems to be robust enough. Here is my code for writing a client that sends a simple GET request to an IPv6 server address.

    Client

    extern crate curl;
    use std::io::{stdout, Write};
    use curl::easy::Easy;
    
    fn main() {
        let mut easy = Easy::new();
    
        easy.url("https://[::1]:3000").unwrap();
        easy.write_function(|data| {
            stdout().write_all(data).unwrap();
            Ok(data.len())
        }).unwrap();
    
        easy.perform().unwrap();
    }
    

    This is not the prettiest solution as it uses a library built in C, which is an unsafe language, but until better alternatives come up it is a nice workaround.