25

How can I force a golang https get request to use a specific IP address. I want to skip DNS resolution and provide the IP myself. The equivalent in curl would be a --resolve as illustrated below.

curl https://domain.com/dir/filename --resolve "domain.com:443:10.10.10.10"

Since this is ssl I want to avoid substituting in the IP for the domain as in the following example.

curl https://10.10.10.10/dir/filename --header "Host: domain.com"

Ryan D.
  • 383
  • 1
  • 5
  • 9

2 Answers2

36

You can provide a custom Transport.DialContext function.

func main() {
    dialer := &net.Dialer{
        Timeout:   30 * time.Second,
        KeepAlive: 30 * time.Second,
        // DualStack: true, // this is deprecated as of go 1.16
    }
    // or create your own transport, there's an example on godoc.
    http.DefaultTransport.(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
        if addr == "google.com:443" {
            addr = "216.58.198.206:443"
        }
        return dialer.DialContext(ctx, network, addr)
    }
    resp, err := http.Get("https://google.com")
    log.Println(resp.Header, err)
}
OneOfOne
  • 95,033
  • 20
  • 184
  • 185
  • Thanks, this worked well, I had to make one adjustment to convert "https" in the "addr" to "443" – Ryan D. Nov 19 '16 at 15:52
  • 1
    @ OneOfOne I ran into an issue when specifying an IPv6 address addr="2001:558:fe0d:23::2:443", I am guessing due to confusion around the colon separator ":". I thought I saw an example where they put [] around the ipv6 addresss like this "[2001:558:fe0d:23::2]:443" and I thought I had this working but it failed as well. Struggling to find IPv6 examples. Any ideas? – Ryan D. Nov 24 '16 at 00:19
  • 1
    I resolved it. the "[]" did work for IPv6. I forgot I was on a VPN that didn't support IPv6 when I was testing. I turned the "ip" and "port" into variables so I could alternate between servers. addr = "[" + ip + "]" + ":" + port now works for both IPv4 and IPv6 – Ryan D. Nov 24 '16 at 01:15
  • @RyanD. you should update your answer with that so it would be a future reference. – OneOfOne Nov 24 '16 at 05:07
  • DualStack is deprecated now. (my go version is 1.16) – codeskyblue May 13 '21 at 02:18
  • what if I'm using proxy? – John Balvin Arias Oct 06 '22 at 21:02
6

OneOfOne's answer above is excellent. I am posting the full working package code here to make it easier for noobs like me. I added a couple of println so that you could see the addr value before and after modification.

package main

import (
    "context"
    "fmt"
    "log"
    "net"
    "net/http"
    "time"
)

func main() {
    dialer := &net.Dialer{
        Timeout:   30 * time.Second,
        KeepAlive: 30 * time.Second,
        DualStack: true,
    }
    // or create your own transport, there's an example on godoc.
    http.DefaultTransport.(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
        fmt.Println("address original =", addr)
        if addr == "google.com:443" {
            addr = "216.58.198.206:443"
            fmt.Println("address modified =", addr)
        }
        return dialer.DialContext(ctx, network, addr)
    }
    resp, err := http.Get("https://google.com")
    log.Println(resp.Header, err)
}
Ryan D.
  • 383
  • 1
  • 5
  • 9