Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: net: an easy way to make Dialer.Dial("tcp", "DNS reg-name") prefer IPv4 to IPv6 or vice versa #14760

Closed
bong0 opened this issue Mar 10, 2016 · 13 comments

Comments

@bong0
Copy link

bong0 commented Mar 10, 2016

Please answer these questions before submitting your issue. Thanks!

  1. What version of Go are you using (go version)?
    go version go1.5.3 linux/amd64
  2. What operating system and processor architecture are you using (go env)?
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/bongo/dev/go"
GORACE=""
GOROOT="/usr/lib/golang"
GOTOOLDIR="/usr/lib/golang/pkg/tool/linux_amd64"
GO15VENDOREXPERIMENT=""
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0"
CXX="g++"
CGO_ENABLED="1"
  1. What did you do?
    I tried to provide a IPv4 and IPv6 address for LocalAddr of a net.Dialer but the model doesn't allow it for now.
  2. What did you expect to see?
    LocalAddr should be either
    • an array of Addr
      • an address of highest prio (v6 first or v4 first) is chosen randomly
    • or a new struct holding a single v4 and v6
      • the according address is chosen by os priority,if available. Else take the existing lower prio or choose automatically if whole LocalAddr is nil (as it is now)
@bradfitz
Copy link
Contributor

/cc @mikioh for thoughts

@bradfitz bradfitz changed the title Add Dual-Stack Support for net.Dialer LocalAddr net: add Dual-Stack Support for Dialer's LocalAddr Mar 10, 2016
@mikioh
Copy link
Contributor

mikioh commented Mar 11, 2016

I suppose that the existing Dialer is designed for handling a single pair of DNS reg-name and address literal or address literals. For handling multiple address families, endpoints and subflows, I'd prefer a new API rather than modifying the existing Dialer, the same as #9334.

@bradfitz
Copy link
Contributor

I don't think we want to add more dialing API when we already have Dialer. Dialer seems like the natural place to extend, and was made exactly to provide a place to extend.

@mikioh
Copy link
Contributor

mikioh commented Mar 11, 2016

Fair enough.

@bong0,

Can you please elaborate on your problem?

  • What's your use case?
  • Why do you need to specify multiple addresses?
  • Does the list of addresses need to support multiple address families?

In general, the additional fields would look like the following:

type Dialer struct {
        // A list of source address candidates for TCP and UDP connections.
        LocalAddrs []Addr

        // For verifying/specifying the addressing scope of LocalAddr and LocalAddrs.
        // Moreover, when LocalAddr and LocalAddrs are empty,
        // source address candidates will be chosen from LocalInterfaces.
        // When the destination address scope is less than or equal to link-local,
        // LocalInterfaces must not contain multiple interfaces. 
        LocalInterfaces []Interface
}

Most hard part would be to write test cases and to prepare test environment.

@bong0
Copy link
Author

bong0 commented Mar 11, 2016

@mikioh sure.
Usecase+Why I need multiple addresses:
Let's take the following example (http://play.golang.org/p/avpRdQNaW8)

I set my LocalAddr to a IPv4 Address assigned to one local interface 192.168.0.99.

func main(){

        localAddr := &net.TCPAddr{
                IP: net.ParseIP("192.168.0.99"), // a secondary local IP I assigned to me
        }

I configure the client to use it:

        client := &http.Client{}
        client.Transport = &http.Transport{
                Dial: (func(network, addr string) (net.Conn, error) {
                        return (&net.Dialer{
                                Timeout:        3 * time.Second,
                                LocalAddr:      nil,
                                DualStack:      false,

... and decide also to accept connections to ipv6 hosts ("tcp" instead of "tcp4")

                        }).Dial("tcp", addr)
                }),
        }

        fmt.Println("local ip to connect", localAddr.IP)

I connect to a IPv6 only host:

        resp,err := client.Get("http://6.ifcfg.me/")
        if err != nil {
                fmt.Println(err)
                return
        }
        a, err := ioutil.ReadAll(resp.Body)
        if err != nil {
                fmt.Println(err)
        } else {
                fmt.Println("actual ip used: ",string(a))
        }
}

... which succeeds ("fallback" to ipv6) but uses an IP address I could not choose, since I could only specify one LocalAddr for one IP family.

I do not know in advance whether a host is ipv6 only and I want to prefer v4 if it's supported. I do not want to use any ipv6 address to connect.
(The same goes for setting a v6 as LocalAddr and not wanting to connect over an arbitrary v4)

To your Question "Does the list of addresses need to support multiple address families?":

  • Yes if we follow the design of an n-tuple of LocalAddresses from which a random one gets chosen (depending on which addressfamily is needed)

@mikioh
Copy link
Contributor

mikioh commented Mar 11, 2016

I think you misunderstand what Dialer.DualStack is. It just enables "Happy Eyeballs" dialing defined in RFC 6555. Even if DualStack is false, Dialer.Dial tries to connect a list of destination addresses when network is "tcp".

I'm still not sure what you want to do, but it looks like you simply want to prefer IPv4 to IPv6. If so, you can write your favorite dial function like the following:

tr := http.Transport{Dial: myDial}
srcs := []net.TCPAddr{
        {IP: ...},
        {IP: ...},
}

func myDial(network, addr string) (c net.Conn, err error) {
        host, port, err := net.SplitHostPort(addr)
        if err != nil {
                return nil, err
        }
        ips, err := net.LookupIP(host)
        if err != nil {
                return nil, err
        }
        d := net.Dialer{Timeout: 3 * time.Second}
        var fallbacks []net.IP
        for _, ip := range ips {
                if ip.To4() != nil {
                        for _, src := range srcs {
                                d.LocalAddr = &src
                                c, err = d.Dial("tcp4", addr)
                                if err != nil {
                                        continue
                                }
                                return
                        }
                }
                if ip.To16() != nil && ip.To4() == nil {
                        fallbacks = append(fallbacks, ip)
                }
        }
        for _, ip := range fallbacks {
                d.LocalAddr = nil
                c, err = d.Dial("tcp6", net.JoinHostPort(ip.String(), port))
                if err != nil {
                        continue
                }
                return
        }
        return nil, errors.New("oops")
}

@mikioh mikioh changed the title net: add Dual-Stack Support for Dialer's LocalAddr net: an easy way to make Dialer.Dial("tcp", "DNS reg-name) prefer IPv4 to IPv6 or vice versa Mar 12, 2016
@mikioh
Copy link
Contributor

mikioh commented Mar 12, 2016

I suppose that what @bong0 wants is just to make Dialer.Dial("tcp", "DNS reg-name") prefer IPv4 to IPv6 or vice versa without writing own connection setup logic. I'm not sure whether adding LocalAddrs and LocalInterfaces to Dialer is sufficient to achieve that purpose.

@mikioh mikioh changed the title net: an easy way to make Dialer.Dial("tcp", "DNS reg-name) prefer IPv4 to IPv6 or vice versa proposal: net: an easy way to make Dialer.Dial("tcp", "DNS reg-name) prefer IPv4 to IPv6 or vice versa Mar 12, 2016
@mikioh mikioh changed the title proposal: net: an easy way to make Dialer.Dial("tcp", "DNS reg-name) prefer IPv4 to IPv6 or vice versa proposal: net: an easy way to make Dialer.Dial("tcp", "DNS reg-name") prefer IPv4 to IPv6 or vice versa Mar 15, 2016
@mikioh mikioh added this to the Unplanned milestone Mar 15, 2016
@bong0
Copy link
Author

bong0 commented Mar 19, 2016

@mikioh sorry for the delay. I looked at your proposal and it sort of solves what I want to do in the end (just not such a low level).

@bradfitz
Copy link
Contributor

If #16672 were implemented, could you just do it yourself?

@adg
Copy link
Contributor

adg commented Sep 26, 2016

Now that we have a Resolver type, this could be done pretty easily with options on a Resolver. Ping @bradfitz.

@bradfitz
Copy link
Contributor

Add two bools, PreferIPv4 and PreferIPv6 to the new *net.Resolver type?

@mikioh
Copy link
Contributor

mikioh commented Oct 18, 2016

Does Resolver.PreferIPv4 give preference to A-record or preference to IPv4 DNS transport, or both?

@rsc
Copy link
Contributor

rsc commented Dec 19, 2016

Go 1.8 will introduce a net.Resolver type that lets you do a name resolution with a timeout (or if you don't care about a timeout, Go 1.7 already had that), so you could do the conversion from host name to IP addresses with (*Resolver).LookupHost and then prioritize the results however you like, including by starting some v4 attempts and then timing out or kicking off new v6 attempts after a certain time. It doesn't seem like Dialer or anything else in package net needs to change to support this.

Please reopen if this doesn't solve your use case. Thanks.

@rsc rsc closed this as completed Dec 19, 2016
@golang golang locked and limited conversation to collaborators Dec 19, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

7 participants
@bradfitz @mikioh @rsc @bong0 @adg @gopherbot and others