123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- // Copyright (C) 2015 The Syncthing Authors.
- //
- // This Source Code Form is subject to the terms of the Mozilla Public
- // License, v. 2.0. If a copy of the MPL was not distributed with this file,
- // You can obtain one at https://mozilla.org/MPL/2.0/.
- package dialer
- import (
- "context"
- "errors"
- "fmt"
- "net"
- "time"
- "github.com/syncthing/syncthing/lib/connections/registry"
- "golang.org/x/net/ipv4"
- "golang.org/x/net/ipv6"
- "golang.org/x/net/proxy"
- )
- var errUnexpectedInterfaceType = errors.New("unexpected interface type")
- // SetTCPOptions sets our default TCP options on a TCP connection, possibly
- // digging through dialerConn to extract the *net.TCPConn
- func SetTCPOptions(conn net.Conn) error {
- switch conn := conn.(type) {
- case dialerConn:
- return SetTCPOptions(conn.Conn)
- case *net.TCPConn:
- var err error
- if err = conn.SetLinger(0); err != nil {
- return err
- }
- if err = conn.SetNoDelay(false); err != nil {
- return err
- }
- if err = conn.SetKeepAlivePeriod(60 * time.Second); err != nil {
- return err
- }
- if err = conn.SetKeepAlive(true); err != nil {
- return err
- }
- return nil
- default:
- return fmt.Errorf("unknown connection type %T", conn)
- }
- }
- func SetTrafficClass(conn net.Conn, class int) error {
- switch conn := conn.(type) {
- case dialerConn:
- return SetTrafficClass(conn.Conn, class)
- case *net.TCPConn:
- e1 := ipv4.NewConn(conn).SetTOS(class)
- e2 := ipv6.NewConn(conn).SetTrafficClass(class)
- if e1 != nil {
- return e1
- }
- return e2
- default:
- return fmt.Errorf("unknown connection type %T", conn)
- }
- }
- func dialContextWithFallback(ctx context.Context, fallback proxy.ContextDialer, network, addr string) (net.Conn, error) {
- dialer, ok := proxy.FromEnvironment().(proxy.ContextDialer)
- if !ok {
- return nil, errUnexpectedInterfaceType
- }
- if dialer == proxy.Direct {
- conn, err := fallback.DialContext(ctx, network, addr)
- l.Debugf("Dialing direct result %s %s: %v %v", network, addr, conn, err)
- return conn, err
- }
- if noFallback {
- conn, err := dialer.DialContext(ctx, network, addr)
- l.Debugf("Dialing no fallback result %s %s: %v %v", network, addr, conn, err)
- if err != nil {
- return nil, err
- }
- return dialerConn{conn, newDialerAddr(network, addr)}, nil
- }
- proxyDialFudgeAddress := func(ctx context.Context, network, address string) (net.Conn, error) {
- conn, err := dialer.DialContext(ctx, network, addr)
- if err != nil {
- return nil, err
- }
- return dialerConn{conn, newDialerAddr(network, addr)}, err
- }
- return dialTwicePreferFirst(ctx, proxyDialFudgeAddress, fallback.DialContext, "proxy", "fallback", network, addr)
- }
- // DialContext dials via context and/or directly, depending on how it is configured.
- // If dialing via proxy and allowing fallback, dialing for both happens simultaneously
- // and the proxy connection is returned if successful.
- func DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
- return dialContextWithFallback(ctx, proxy.Direct, network, addr)
- }
- // DialContextReusePort tries dialing via proxy if a proxy is configured, and falls back to
- // a direct connection reusing the port from the connections registry, if no proxy is defined, or connecting via proxy
- // fails. It also in parallel dials without reusing the port, just in case reusing the port affects routing decisions badly.
- func DialContextReusePortFunc(registry *registry.Registry) func(ctx context.Context, network, addr string) (net.Conn, error) {
- return func(ctx context.Context, network, addr string) (net.Conn, error) {
- // If proxy is configured, there is no point trying to reuse listen addresses.
- if proxy.FromEnvironment() != proxy.Direct {
- return DialContext(ctx, network, addr)
- }
- localAddrInterface := registry.Get(network, func(addr interface{}) bool {
- return addr.(*net.TCPAddr).IP.IsUnspecified()
- })
- if localAddrInterface == nil {
- // Nothing listening, nothing to reuse.
- return DialContext(ctx, network, addr)
- }
- laddr, ok := localAddrInterface.(*net.TCPAddr)
- if !ok {
- return nil, errUnexpectedInterfaceType
- }
- // Dial twice, once reusing the listen address, another time not reusing it, just in case reusing the address
- // influences routing and we fail to reach our destination.
- dialer := net.Dialer{
- Control: ReusePortControl,
- LocalAddr: laddr,
- }
- return dialTwicePreferFirst(ctx, dialer.DialContext, (&net.Dialer{}).DialContext, "reuse", "non-reuse", network, addr)
- }
- }
- type dialFunc func(ctx context.Context, network, address string) (net.Conn, error)
- func dialTwicePreferFirst(ctx context.Context, first, second dialFunc, firstName, secondName, network, address string) (net.Conn, error) {
- // Delay second dial by some time.
- sleep := time.Second
- if deadline, ok := ctx.Deadline(); ok {
- timeout := time.Until(deadline)
- if timeout > 0 {
- sleep = timeout / 3
- }
- }
- ctx, cancel := context.WithCancel(ctx)
- defer cancel()
- var firstConn, secondConn net.Conn
- var firstErr, secondErr error
- firstDone := make(chan struct{})
- secondDone := make(chan struct{})
- go func() {
- firstConn, firstErr = first(ctx, network, address)
- l.Debugf("Dialing %s result %s %s: %v %v", firstName, network, address, firstConn, firstErr)
- close(firstDone)
- }()
- go func() {
- select {
- case <-firstDone:
- if firstErr == nil {
- // First succeeded, no point doing anything in second
- secondErr = errors.New("didn't dial")
- close(secondDone)
- return
- }
- case <-ctx.Done():
- secondErr = ctx.Err()
- close(secondDone)
- return
- case <-time.After(sleep):
- }
- secondConn, secondErr = second(ctx, network, address)
- l.Debugf("Dialing %s result %s %s: %v %v", secondName, network, address, secondConn, secondErr)
- close(secondDone)
- }()
- <-firstDone
- if firstErr == nil {
- go func() {
- <-secondDone
- if secondConn != nil {
- _ = secondConn.Close()
- }
- }()
- return firstConn, firstErr
- }
- <-secondDone
- return secondConn, secondErr
- }
|