14

Curl has a feature for manually specifying which IP to resolve a host to. For example:

curl https://google.com --resolve "google.com:443:173.194.72.113"

This is particularly useful when using HTTPS. If it was just a HTTP request, I could have achieved the same by specifying the IP address directly, and adding a host header. But in HTTPS that would break the connection since the SSL certificate host would be compared to the IP address and not the host header.

My question is, how can I achieve the same thing in Java?

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
GreatFire
  • 427
  • 1
  • 5
  • 11
  • When using Apache httpClient there is a common workaround for the problem. It uses a complete different strategy to allow these types of SSL connection. Have a look at http://stackoverflow.com/questions/2642777/trusting-all-certificates-using-httpclient-over-https – blafasel Jun 22 '14 at 10:20
  • Thanks, I know about that workaround, but it shouldn't be necessary as illustrated by how it's done by curl. I don't want to trust all certificates or manually trust one given certificate. I just want to decide which IP address to resolve the given host to. I may end up using a workaround like that but I'd like to know first if there's a better solution. – GreatFire Jun 22 '14 at 12:17
  • Depending on your situation another possibility is to just add the mapping you want to the /etc/hosts (or equiv) file. – John Hascall Feb 11 '16 at 03:32

2 Answers2

22

If using Apache's HttpClient, you can create a custom DNS resolver to detect the host you'd like to redirect, and then provide a substitute IP address.

Note: Just changing the Host header for HTTPS requests doesn't work. It will throw "javax.net.ssl.SSLPeerUnverifiedException", forcing you to trust bad certificates, stop SNI from working, etc., so really not an option. A custom DnsResolver is the only clean way I've found to get these requests to work with HTTPS in Java.

Example:

/* Custom DNS resolver */
DnsResolver dnsResolver = new SystemDefaultDnsResolver() {
    @Override
    public InetAddress[] resolve(final String host) throws UnknownHostException {
        if (host.equalsIgnoreCase("my.host.com")) {
            /* If we match the host we're trying to talk to, 
               return the IP address we want, not what is in DNS */
            return new InetAddress[] { InetAddress.getByName("127.0.0.1") };
        } else {
            /* Else, resolve it as we would normally */
            return super.resolve(host);
        }
    }
};

/* HttpClientConnectionManager allows us to use custom DnsResolver */
BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager(
    /* We're forced to create a SocketFactory Registry.  Passing null
       doesn't force a default Registry, so we re-invent the wheel. */
    RegistryBuilder.<ConnectionSocketFactory>create()
        .register("http", PlainConnectionSocketFactory.getSocketFactory())
        .register("https", SSLConnectionSocketFactory.getSocketFactory())
        .build(), 
    null, /* Default ConnectionFactory */ 
    null, /* Default SchemePortResolver */ 
    dnsResolver  /* Our DnsResolver */
    );

/* build HttpClient that will use our DnsResolver */
HttpClient httpClient = HttpClientBuilder.create()
        .setConnectionManager(connManager)
        .build();

/* build our request */
HttpGet httpRequest = new HttpGet("https://my.host.com/page?and=stuff"); 

/* Executing our request should now hit 127.0.0.1, regardless of DNS */
HttpResponse httpResponse = httpClient.execute(httpRequest);
JohnK
  • 397
  • 4
  • 10
0

I don't have the code close at hand, but you can also write your own SSL handler/checker that could adapt or flat-out just ignore all the security. Using the JDK base networking, we had to totally ignore SSL certificates internally for testing. Should be easy to find examples.

Scott Sosna
  • 1,443
  • 1
  • 8
  • 8