Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to override DNS in HTTP connections in Java

Tags:

java

https

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?

like image 254
GreatFire Avatar asked Jun 22 '14 10:06

GreatFire


1 Answers

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);
like image 59
JohnK Avatar answered Nov 16 '22 02:11

JohnK