Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind an http.Client in Go to an IP Address

Tags:

http

go

ip

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?

like image 977
jordan2175 Avatar asked Dec 01 '22 13:12

jordan2175


2 Answers

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,
    },
}
like image 68
Tim Cooper Avatar answered Dec 15 '22 05:12

Tim Cooper


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))
}
like image 28
jordan2175 Avatar answered Dec 15 '22 03:12

jordan2175