2

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.

Misho Janev
  • 512
  • 5
  • 13
  • Please [edit] your question to contain a [MCVE] of your client. You should also include what version of hyper you are using. – Shepmaster Mar 27 '18 at 17:11
  • Can you even completely remove hyper from the program? Does something like `std::net::TcpStream::connect(("[::1]", 3005)).unwrap();` or `std::net::TcpStream::connect("[::1]:3005").unwrap();` reproduce the same behavior in both cases? – Shepmaster Mar 27 '18 at 17:15
  • You could try to remove the `http://`-part; I don't know how `hyper` works internally but it might get confused about this. – K. Biermann Mar 27 '18 at 19:55
  • @Shepmaster I have added a minimal (I believe) set of code that you can use to reproduce this behaviour and you are correct for asking me to specify the version of hyper, as I remembered I forced that to be the latest one from the 0.10.X series, as I had some troubles with 0.11.X. So to clarify this, for these examples 0.10.13 was used. – Misho Janev Mar 27 '18 at 20:32

1 Answers1

0

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Misho Janev
  • 512
  • 5
  • 13