kainlite
kainlite

Reputation: 1061

How to set which IP to use for a HTTP request?

I dont know if it's possible as the std lib does not state anything about the current address being used:

http://golang.org/pkg/net/http/

resp, err := http.Get("http://example.com/")
if err != nil {
    // handle error
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)

What I'm trying to do is set the source address for that http request, why? because I don't want to use my primary ip address for that kind of stuff...

Upvotes: 12

Views: 9831

Answers (2)

SadPencil
SadPencil

Reputation: 71

Setting up a custom dialer with the specify IP works sometimes, but it is weird since sometimes it failed to work, while curl works normally. After checking the source code of curl, I figured out why curl is able to specify what network interface to be used: SO_BINDTODEVICE. Unfortunately this is a Linux thing.

So compared to JimB's answer, my method has:

  • Pro: Behaves as stable as curl, which is irrelevant from routing tables
  • Con: Only support Linux. So you probably want to learn more about the build tags to write platform-specific code.
dialer := &net.Dialer{
    Control: func(network, address string, conn syscall.RawConn) error {
        var operr error
        if err := conn.Control(func(fd uintptr) {
            operr = unix.BindToDevice(int(fd), forceNetworkInterface)
        }); err != nil {
            return err
        }
        return operr
    },
}

client = http.Client{
    Transport = &http.Transport{
        DialContext: dialer.DialContext,
    },
}

In addition, curl performs SO_BINDTODEVICE which behaves like the above code. And for non-Linux platforms, or when SO_BINDTODEVICE fails, curl sets the local IP address just as JimB's answer does. So you can first try my code and then use JimB's answer as a fallback.

See the source code of curl for details.

Upvotes: 0

Mr_Pink
Mr_Pink

Reputation: 109357

You can set a custom Dialer in the Client's Transport.

// Create a transport like http.DefaultTransport, but with a specified localAddr
transport := &http.Transport{
    Proxy: http.ProxyFromEnvironment,
    DialContext: (&net.Dialer{
        Timeout:   30 * time.Second,
        KeepAlive: 30 * time.Second,
        LocalAddr: localAddr,
        DualStack: true,
    }).DialContext,
    MaxIdleConns:          100,
    IdleConnTimeout:       90 * time.Second,
    TLSHandshakeTimeout:   10 * time.Second,
    ExpectContinueTimeout: 1 * time.Second,
}

client := &http.Client{
    Transport: transport,
}

Upvotes: 27

Related Questions