I have a client machine with multiple NICs, how do I bind an http.Client in Go to a certain NIC or to a certain SRC IP Address?
Say you have some very basic http client code that looks like:
package main
import (
"net/http"
)
func main() {
webclient := &http.Client{}
req, _ := http.NewRequest("GET", "http://www.google.com", nil)
httpResponse, _ := webclient.Do(req)
defer httpResponse.Body.Close()
}
Is there a way to bind to a certain NIC or IP?
Similar to this question, you need to set the http.Client.Transport
field. Setting it to an instance of net.Transport
allows you to specify which net.Dialer
you want to use. net.Dialer
then allows you to specify the local address to make connections from.
Example:
localAddr, err := net.ResolveIPAddr("ip", "<my local address>")
if err != nil {
panic(err)
}
// You also need to do this to make it work and not give you a
// "mismatched local address type ip"
// This will make the ResolveIPAddr a TCPAddr without needing to
// say what SRC port number to use.
localTCPAddr := net.TCPAddr{
IP: localAddr.IP,
}
webclient := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
LocalAddr: &localTCPAddr,
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
Here is a fully working example that incorporates the answer from Tim. I also broke out all of the nested pieces to make it easier to read and learn from.
package main
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"time"
)
func main() {
localAddr, err := net.ResolveIPAddr("ip", "10.128.64.219")
if err != nil {
panic(err)
}
localTCPAddr := net.TCPAddr{
IP: localAddr.IP,
}
d := net.Dialer{
LocalAddr: &localTCPAddr,
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: d.Dial,
TLSHandshakeTimeout: 10 * time.Second,
}
webclient := &http.Client{Transport: tr}
// Use NewRequest so we can change the UserAgent string in the header
req, err := http.NewRequest("GET", "http://www.google.com:80", nil)
if err != nil {
panic(err)
}
res, err := webclient.Do(req)
if err != nil {
panic(err)
}
fmt.Println("DEBUG", res)
defer res.Body.Close()
content, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Printf("%s", string(content))
}