syncthing/lib/dialer/public.go

108 lines
2.7 KiB
Go

// 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"
"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 *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 *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 {
return fallback.DialContext(ctx, network, addr)
}
if noFallback {
return dialer.DialContext(ctx, network, addr)
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
var proxyConn, fallbackConn net.Conn
var proxyErr, fallbackErr error
proxyDone := make(chan struct{})
fallbackDone := make(chan struct{})
go func() {
proxyConn, proxyErr = dialer.DialContext(ctx, network, addr)
close(proxyDone)
}()
go func() {
fallbackConn, fallbackErr = fallback.DialContext(ctx, network, addr)
close(fallbackDone)
}()
<-proxyDone
if proxyErr == nil {
go func() {
<-fallbackDone
if fallbackErr == nil {
fallbackConn.Close()
}
}()
return proxyConn, nil
}
<-fallbackDone
return fallbackConn, fallbackErr
}
// 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)
}