From 985ea29940212e3d88597744100cf0c42c9c74b8 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Mon, 12 Oct 2015 19:30:14 +0100 Subject: [PATCH 1/4] Add proxy support (fixes #271) --- cmd/syncthing/usage_report.go | 3 +- lib/connections/connections_tcp.go | 5 +- lib/dialer/dialer.go | 88 ++++++++++++++++++++++++++++++ lib/relay/client/client.go | 3 +- lib/relay/client/methods.go | 7 ++- lib/upnp/upnp.go | 3 +- 6 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 lib/dialer/dialer.go diff --git a/cmd/syncthing/usage_report.go b/cmd/syncthing/usage_report.go index 7f21e71d..06a8a2bd 100644 --- a/cmd/syncthing/usage_report.go +++ b/cmd/syncthing/usage_report.go @@ -20,6 +20,7 @@ import ( "time" "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/model" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/upgrade" @@ -261,7 +262,7 @@ func (s *usageReportingService) sendUsageReport() error { if BuildEnv == "android" { // This works around the lack of DNS resolution on Android... :( transp.Dial = func(network, addr string) (net.Conn, error) { - return net.Dial(network, "194.126.249.13:443") + return dialer.Dial(network, "194.126.249.13:443") } } diff --git a/lib/connections/connections_tcp.go b/lib/connections/connections_tcp.go index 1a2b4955..5043c81d 100644 --- a/lib/connections/connections_tcp.go +++ b/lib/connections/connections_tcp.go @@ -12,6 +12,7 @@ import ( "net/url" "strings" + "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/model" "github.com/syncthing/syncthing/lib/osutil" ) @@ -37,13 +38,13 @@ func tcpDialer(uri *url.URL, tlsCfg *tls.Config) (*tls.Conn, error) { return nil, err } - conn, err := net.DialTCP("tcp", nil, raddr) + conn, err := dialer.Dial(raddr.Network(), raddr.String()) if err != nil { l.Debugln(err) return nil, err } - err = osutil.SetTCPOptions(conn) + err = osutil.SetTCPOptions(conn.(*net.TCPConn)) if err != nil { l.Infoln(err) } diff --git a/lib/dialer/dialer.go b/lib/dialer/dialer.go new file mode 100644 index 00000000..7d9d5b43 --- /dev/null +++ b/lib/dialer/dialer.go @@ -0,0 +1,88 @@ +// 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 http://mozilla.org/MPL/2.0/. + +package dialer + +import ( + "net" + "os" + "strings" + "time" + + "golang.org/x/net/proxy" + + "github.com/syncthing/syncthing/lib/logger" +) + +var ( + l = logger.DefaultLogger.NewFacility("dialer", "Dialing connections") + dialer = proxy.FromEnvironment() + usingProxy = dialer != proxy.Direct +) + +func init() { + l.SetDebug("dialer", strings.Contains(os.Getenv("STTRACE"), "dialer") || os.Getenv("STTRACE") == "all") + if usingProxy { + // Defer this, so that logging gets setup. + go func() { + time.Sleep(500 * time.Millisecond) + l.Infoln("Proxy settings detected") + }() + } +} + +// Dial tries dialing via proxy if a proxy is configured, and falls back to +// a direct connection if no proxy is defined, or connecting via proxy fails. +func Dial(network, addr string) (net.Conn, error) { + if usingProxy { + conn, err := dialer.Dial(network, addr) + if err == nil { + l.Debugf("Dialing %s address %s via proxy - success, %s -> %s", network, addr, conn.LocalAddr(), conn.RemoteAddr()) + return dialerConn{ + conn, newDialerAddr(network, addr), + }, nil + } + l.Debugf("Dialing %s address %s via proxy - error %s", network, addr, err) + } + + conn, err := proxy.Direct.Dial(network, addr) + if err == nil { + l.Debugf("Dialing %s address %s directly - success, %s -> %s", network, addr, conn.LocalAddr(), conn.RemoteAddr()) + } else { + l.Debugf("Dialing %s address %s directly - error %s", network, addr, err) + } + return conn, err +} + +type dialerConn struct { + net.Conn + addr net.Addr +} + +func (c dialerConn) RemoteAddr() net.Addr { + return c.addr +} + +func newDialerAddr(network, addr string) net.Addr { + netaddr, err := net.ResolveIPAddr(network, addr) + if err == nil { + return netaddr + } + return fallbackAddr{network, addr} +} + +type fallbackAddr struct { + network string + addr string +} + +func (a fallbackAddr) Network() string { + return a.network +} + +func (a fallbackAddr) String() string { + return a.addr +} diff --git a/lib/relay/client/client.go b/lib/relay/client/client.go index e86099a1..aeef8f99 100644 --- a/lib/relay/client/client.go +++ b/lib/relay/client/client.go @@ -9,6 +9,7 @@ import ( "net/url" "time" + "github.com/syncthing/syncthing/lib/dialer" syncthingprotocol "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/relay/protocol" "github.com/syncthing/syncthing/lib/sync" @@ -172,7 +173,7 @@ func (c *ProtocolClient) connect() error { } t0 := time.Now() - tcpConn, err := net.Dial("tcp", c.URI.Host) + tcpConn, err := dialer.Dial("tcp", c.URI.Host) if err != nil { return err } diff --git a/lib/relay/client/methods.go b/lib/relay/client/methods.go index 46afc760..92a7e9b0 100644 --- a/lib/relay/client/methods.go +++ b/lib/relay/client/methods.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/syncthing/syncthing/lib/dialer" syncthingprotocol "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/relay/protocol" ) @@ -20,10 +21,12 @@ func GetInvitationFromRelay(uri *url.URL, id syncthingprotocol.DeviceID, certs [ return protocol.SessionInvitation{}, fmt.Errorf("Unsupported relay scheme: %v", uri.Scheme) } - conn, err := tls.Dial("tcp", uri.Host, configForCerts(certs)) + rconn, err := dialer.Dial("tcp", uri.Host) if err != nil { return protocol.SessionInvitation{}, err } + + conn := tls.Client(rconn, configForCerts(certs)) conn.SetDeadline(time.Now().Add(10 * time.Second)) if err := performHandshakeAndValidation(conn, uri); err != nil { @@ -63,7 +66,7 @@ func GetInvitationFromRelay(uri *url.URL, id syncthingprotocol.DeviceID, certs [ func JoinSession(invitation protocol.SessionInvitation) (net.Conn, error) { addr := net.JoinHostPort(net.IP(invitation.Address).String(), strconv.Itoa(int(invitation.Port))) - conn, err := net.Dial("tcp", addr) + conn, err := dialer.Dial("tcp", addr) if err != nil { return nil, err } diff --git a/lib/upnp/upnp.go b/lib/upnp/upnp.go index 151c2999..f8e4cf9e 100644 --- a/lib/upnp/upnp.go +++ b/lib/upnp/upnp.go @@ -25,6 +25,7 @@ import ( "strings" "time" + "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/sync" ) @@ -289,7 +290,7 @@ func parseResponse(deviceType string, resp []byte) (IGD, error) { } func localIP(url *url.URL) (string, error) { - conn, err := net.Dial("tcp", url.Host) + conn, err := dialer.Dial("tcp", url.Host) if err != nil { return "", err } From db494f2afcbad71f59ce0ad98f721ad1d4b7ed6d Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Mon, 12 Oct 2015 19:53:15 +0100 Subject: [PATCH 2/4] God damn godeps --- Godeps/Godeps.json | 8 +- .../golang.org/x/net/internal/iana/const.go | 2 +- .../src/golang.org/x/net/ipv6/doc.go | 6 +- .../src/golang.org/x/net/proxy/direct.go | 18 ++ .../src/golang.org/x/net/proxy/per_host.go | 140 ++++++++++++ .../golang.org/x/net/proxy/per_host_test.go | 55 +++++ .../src/golang.org/x/net/proxy/proxy.go | 94 ++++++++ .../src/golang.org/x/net/proxy/proxy_test.go | 142 ++++++++++++ .../src/golang.org/x/net/proxy/socks5.go | 210 ++++++++++++++++++ 9 files changed, 669 insertions(+), 6 deletions(-) create mode 100644 Godeps/_workspace/src/golang.org/x/net/proxy/direct.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/proxy/per_host.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/proxy/per_host_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/proxy/proxy.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/proxy/proxy_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/net/proxy/socks5.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 16a6182c..951257f8 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -64,11 +64,15 @@ }, { "ImportPath": "golang.org/x/net/internal/iana", - "Rev": "db8e4de5b2d6653f66aea53094624468caad15d2" + "Rev": "4b709d93778b93d2f34943e3142c71578d83ad31" }, { "ImportPath": "golang.org/x/net/ipv6", - "Rev": "db8e4de5b2d6653f66aea53094624468caad15d2" + "Rev": "4b709d93778b93d2f34943e3142c71578d83ad31" + }, + { + "ImportPath": "golang.org/x/net/proxy", + "Rev": "4b709d93778b93d2f34943e3142c71578d83ad31" }, { "ImportPath": "golang.org/x/text/transform", diff --git a/Godeps/_workspace/src/golang.org/x/net/internal/iana/const.go b/Godeps/_workspace/src/golang.org/x/net/internal/iana/const.go index 9c62872e..7fe88225 100644 --- a/Godeps/_workspace/src/golang.org/x/net/internal/iana/const.go +++ b/Godeps/_workspace/src/golang.org/x/net/internal/iana/const.go @@ -2,7 +2,7 @@ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // Package iana provides protocol number resources managed by the Internet Assigned Numbers Authority (IANA). -package iana +package iana // import "golang.org/x/net/internal/iana" // Differentiated Services Field Codepoints (DSCP), Updated: 2013-06-25 const ( diff --git a/Godeps/_workspace/src/golang.org/x/net/ipv6/doc.go b/Godeps/_workspace/src/golang.org/x/net/ipv6/doc.go index 9facad9e..3c15c21f 100644 --- a/Godeps/_workspace/src/golang.org/x/net/ipv6/doc.go +++ b/Godeps/_workspace/src/golang.org/x/net/ipv6/doc.go @@ -42,7 +42,7 @@ // The outgoing packets will be labeled DiffServ assured forwarding // class 1 low drop precedence, known as AF11 packets. // -// if err := ipv6.NewConn(c).SetTrafficClass(DiffServAF11); err != nil { +// if err := ipv6.NewConn(c).SetTrafficClass(0x28); err != nil { // // error handling // } // if _, err := c.Write(data); err != nil { @@ -124,7 +124,7 @@ // // The application can also send both unicast and multicast packets. // -// p.SetTrafficClass(DiffServCS0) +// p.SetTrafficClass(0x0) // p.SetHopLimit(16) // if _, err := p.WriteTo(data[:n], nil, src); err != nil { // // error handling @@ -237,4 +237,4 @@ // MLDv1 and starts to listen to multicast traffic. // In the fallback case, ExcludeSourceSpecificGroup and // IncludeSourceSpecificGroup may return an error. -package ipv6 +package ipv6 // import "golang.org/x/net/ipv6" diff --git a/Godeps/_workspace/src/golang.org/x/net/proxy/direct.go b/Godeps/_workspace/src/golang.org/x/net/proxy/direct.go new file mode 100644 index 00000000..4c5ad88b --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/proxy/direct.go @@ -0,0 +1,18 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxy + +import ( + "net" +) + +type direct struct{} + +// Direct is a direct proxy: one that makes network connections directly. +var Direct = direct{} + +func (direct) Dial(network, addr string) (net.Conn, error) { + return net.Dial(network, addr) +} diff --git a/Godeps/_workspace/src/golang.org/x/net/proxy/per_host.go b/Godeps/_workspace/src/golang.org/x/net/proxy/per_host.go new file mode 100644 index 00000000..f540b196 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/proxy/per_host.go @@ -0,0 +1,140 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxy + +import ( + "net" + "strings" +) + +// A PerHost directs connections to a default Dialer unless the hostname +// requested matches one of a number of exceptions. +type PerHost struct { + def, bypass Dialer + + bypassNetworks []*net.IPNet + bypassIPs []net.IP + bypassZones []string + bypassHosts []string +} + +// NewPerHost returns a PerHost Dialer that directs connections to either +// defaultDialer or bypass, depending on whether the connection matches one of +// the configured rules. +func NewPerHost(defaultDialer, bypass Dialer) *PerHost { + return &PerHost{ + def: defaultDialer, + bypass: bypass, + } +} + +// Dial connects to the address addr on the given network through either +// defaultDialer or bypass. +func (p *PerHost) Dial(network, addr string) (c net.Conn, err error) { + host, _, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + + return p.dialerForRequest(host).Dial(network, addr) +} + +func (p *PerHost) dialerForRequest(host string) Dialer { + if ip := net.ParseIP(host); ip != nil { + for _, net := range p.bypassNetworks { + if net.Contains(ip) { + return p.bypass + } + } + for _, bypassIP := range p.bypassIPs { + if bypassIP.Equal(ip) { + return p.bypass + } + } + return p.def + } + + for _, zone := range p.bypassZones { + if strings.HasSuffix(host, zone) { + return p.bypass + } + if host == zone[1:] { + // For a zone "example.com", we match "example.com" + // too. + return p.bypass + } + } + for _, bypassHost := range p.bypassHosts { + if bypassHost == host { + return p.bypass + } + } + return p.def +} + +// AddFromString parses a string that contains comma-separated values +// specifying hosts that should use the bypass proxy. Each value is either an +// IP address, a CIDR range, a zone (*.example.com) or a hostname +// (localhost). A best effort is made to parse the string and errors are +// ignored. +func (p *PerHost) AddFromString(s string) { + hosts := strings.Split(s, ",") + for _, host := range hosts { + host = strings.TrimSpace(host) + if len(host) == 0 { + continue + } + if strings.Contains(host, "/") { + // We assume that it's a CIDR address like 127.0.0.0/8 + if _, net, err := net.ParseCIDR(host); err == nil { + p.AddNetwork(net) + } + continue + } + if ip := net.ParseIP(host); ip != nil { + p.AddIP(ip) + continue + } + if strings.HasPrefix(host, "*.") { + p.AddZone(host[1:]) + continue + } + p.AddHost(host) + } +} + +// AddIP specifies an IP address that will use the bypass proxy. Note that +// this will only take effect if a literal IP address is dialed. A connection +// to a named host will never match an IP. +func (p *PerHost) AddIP(ip net.IP) { + p.bypassIPs = append(p.bypassIPs, ip) +} + +// AddNetwork specifies an IP range that will use the bypass proxy. Note that +// this will only take effect if a literal IP address is dialed. A connection +// to a named host will never match. +func (p *PerHost) AddNetwork(net *net.IPNet) { + p.bypassNetworks = append(p.bypassNetworks, net) +} + +// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of +// "example.com" matches "example.com" and all of its subdomains. +func (p *PerHost) AddZone(zone string) { + if strings.HasSuffix(zone, ".") { + zone = zone[:len(zone)-1] + } + if !strings.HasPrefix(zone, ".") { + zone = "." + zone + } + p.bypassZones = append(p.bypassZones, zone) +} + +// AddHost specifies a hostname that will use the bypass proxy. +func (p *PerHost) AddHost(host string) { + if strings.HasSuffix(host, ".") { + host = host[:len(host)-1] + } + p.bypassHosts = append(p.bypassHosts, host) +} diff --git a/Godeps/_workspace/src/golang.org/x/net/proxy/per_host_test.go b/Godeps/_workspace/src/golang.org/x/net/proxy/per_host_test.go new file mode 100644 index 00000000..a7d80957 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/proxy/per_host_test.go @@ -0,0 +1,55 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxy + +import ( + "errors" + "net" + "reflect" + "testing" +) + +type recordingProxy struct { + addrs []string +} + +func (r *recordingProxy) Dial(network, addr string) (net.Conn, error) { + r.addrs = append(r.addrs, addr) + return nil, errors.New("recordingProxy") +} + +func TestPerHost(t *testing.T) { + var def, bypass recordingProxy + perHost := NewPerHost(&def, &bypass) + perHost.AddFromString("localhost,*.zone,127.0.0.1,10.0.0.1/8,1000::/16") + + expectedDef := []string{ + "example.com:123", + "1.2.3.4:123", + "[1001::]:123", + } + expectedBypass := []string{ + "localhost:123", + "zone:123", + "foo.zone:123", + "127.0.0.1:123", + "10.1.2.3:123", + "[1000::]:123", + } + + for _, addr := range expectedDef { + perHost.Dial("tcp", addr) + } + for _, addr := range expectedBypass { + perHost.Dial("tcp", addr) + } + + if !reflect.DeepEqual(expectedDef, def.addrs) { + t.Errorf("Hosts which went to the default proxy didn't match. Got %v, want %v", def.addrs, expectedDef) + } + if !reflect.DeepEqual(expectedBypass, bypass.addrs) { + t.Errorf("Hosts which went to the bypass proxy didn't match. Got %v, want %v", bypass.addrs, expectedBypass) + } +} diff --git a/Godeps/_workspace/src/golang.org/x/net/proxy/proxy.go b/Godeps/_workspace/src/golang.org/x/net/proxy/proxy.go new file mode 100644 index 00000000..78a8b7be --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/proxy/proxy.go @@ -0,0 +1,94 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package proxy provides support for a variety of protocols to proxy network +// data. +package proxy // import "golang.org/x/net/proxy" + +import ( + "errors" + "net" + "net/url" + "os" +) + +// A Dialer is a means to establish a connection. +type Dialer interface { + // Dial connects to the given address via the proxy. + Dial(network, addr string) (c net.Conn, err error) +} + +// Auth contains authentication parameters that specific Dialers may require. +type Auth struct { + User, Password string +} + +// FromEnvironment returns the dialer specified by the proxy related variables in +// the environment. +func FromEnvironment() Dialer { + allProxy := os.Getenv("all_proxy") + if len(allProxy) == 0 { + return Direct + } + + proxyURL, err := url.Parse(allProxy) + if err != nil { + return Direct + } + proxy, err := FromURL(proxyURL, Direct) + if err != nil { + return Direct + } + + noProxy := os.Getenv("no_proxy") + if len(noProxy) == 0 { + return proxy + } + + perHost := NewPerHost(proxy, Direct) + perHost.AddFromString(noProxy) + return perHost +} + +// proxySchemes is a map from URL schemes to a function that creates a Dialer +// from a URL with such a scheme. +var proxySchemes map[string]func(*url.URL, Dialer) (Dialer, error) + +// RegisterDialerType takes a URL scheme and a function to generate Dialers from +// a URL with that scheme and a forwarding Dialer. Registered schemes are used +// by FromURL. +func RegisterDialerType(scheme string, f func(*url.URL, Dialer) (Dialer, error)) { + if proxySchemes == nil { + proxySchemes = make(map[string]func(*url.URL, Dialer) (Dialer, error)) + } + proxySchemes[scheme] = f +} + +// FromURL returns a Dialer given a URL specification and an underlying +// Dialer for it to make network requests. +func FromURL(u *url.URL, forward Dialer) (Dialer, error) { + var auth *Auth + if u.User != nil { + auth = new(Auth) + auth.User = u.User.Username() + if p, ok := u.User.Password(); ok { + auth.Password = p + } + } + + switch u.Scheme { + case "socks5": + return SOCKS5("tcp", u.Host, auth, forward) + } + + // If the scheme doesn't match any of the built-in schemes, see if it + // was registered by another package. + if proxySchemes != nil { + if f, ok := proxySchemes[u.Scheme]; ok { + return f(u, forward) + } + } + + return nil, errors.New("proxy: unknown scheme: " + u.Scheme) +} diff --git a/Godeps/_workspace/src/golang.org/x/net/proxy/proxy_test.go b/Godeps/_workspace/src/golang.org/x/net/proxy/proxy_test.go new file mode 100644 index 00000000..c19a5c06 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/proxy/proxy_test.go @@ -0,0 +1,142 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxy + +import ( + "io" + "net" + "net/url" + "strconv" + "sync" + "testing" +) + +func TestFromURL(t *testing.T) { + endSystem, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer endSystem.Close() + gateway, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer gateway.Close() + + var wg sync.WaitGroup + wg.Add(1) + go socks5Gateway(t, gateway, endSystem, socks5Domain, &wg) + + url, err := url.Parse("socks5://user:password@" + gateway.Addr().String()) + if err != nil { + t.Fatalf("url.Parse failed: %v", err) + } + proxy, err := FromURL(url, Direct) + if err != nil { + t.Fatalf("FromURL failed: %v", err) + } + _, port, err := net.SplitHostPort(endSystem.Addr().String()) + if err != nil { + t.Fatalf("net.SplitHostPort failed: %v", err) + } + if c, err := proxy.Dial("tcp", "localhost:"+port); err != nil { + t.Fatalf("FromURL.Dial failed: %v", err) + } else { + c.Close() + } + + wg.Wait() +} + +func TestSOCKS5(t *testing.T) { + endSystem, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer endSystem.Close() + gateway, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen failed: %v", err) + } + defer gateway.Close() + + var wg sync.WaitGroup + wg.Add(1) + go socks5Gateway(t, gateway, endSystem, socks5IP4, &wg) + + proxy, err := SOCKS5("tcp", gateway.Addr().String(), nil, Direct) + if err != nil { + t.Fatalf("SOCKS5 failed: %v", err) + } + if c, err := proxy.Dial("tcp", endSystem.Addr().String()); err != nil { + t.Fatalf("SOCKS5.Dial failed: %v", err) + } else { + c.Close() + } + + wg.Wait() +} + +func socks5Gateway(t *testing.T, gateway, endSystem net.Listener, typ byte, wg *sync.WaitGroup) { + defer wg.Done() + + c, err := gateway.Accept() + if err != nil { + t.Errorf("net.Listener.Accept failed: %v", err) + return + } + defer c.Close() + + b := make([]byte, 32) + var n int + if typ == socks5Domain { + n = 4 + } else { + n = 3 + } + if _, err := io.ReadFull(c, b[:n]); err != nil { + t.Errorf("io.ReadFull failed: %v", err) + return + } + if _, err := c.Write([]byte{socks5Version, socks5AuthNone}); err != nil { + t.Errorf("net.Conn.Write failed: %v", err) + return + } + if typ == socks5Domain { + n = 16 + } else { + n = 10 + } + if _, err := io.ReadFull(c, b[:n]); err != nil { + t.Errorf("io.ReadFull failed: %v", err) + return + } + if b[0] != socks5Version || b[1] != socks5Connect || b[2] != 0x00 || b[3] != typ { + t.Errorf("got an unexpected packet: %#02x %#02x %#02x %#02x", b[0], b[1], b[2], b[3]) + return + } + if typ == socks5Domain { + copy(b[:5], []byte{socks5Version, 0x00, 0x00, socks5Domain, 9}) + b = append(b, []byte("localhost")...) + } else { + copy(b[:4], []byte{socks5Version, 0x00, 0x00, socks5IP4}) + } + host, port, err := net.SplitHostPort(endSystem.Addr().String()) + if err != nil { + t.Errorf("net.SplitHostPort failed: %v", err) + return + } + b = append(b, []byte(net.ParseIP(host).To4())...) + p, err := strconv.Atoi(port) + if err != nil { + t.Errorf("strconv.Atoi failed: %v", err) + return + } + b = append(b, []byte{byte(p >> 8), byte(p)}...) + if _, err := c.Write(b); err != nil { + t.Errorf("net.Conn.Write failed: %v", err) + return + } +} diff --git a/Godeps/_workspace/src/golang.org/x/net/proxy/socks5.go b/Godeps/_workspace/src/golang.org/x/net/proxy/socks5.go new file mode 100644 index 00000000..9b962823 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/net/proxy/socks5.go @@ -0,0 +1,210 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proxy + +import ( + "errors" + "io" + "net" + "strconv" +) + +// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address +// with an optional username and password. See RFC 1928. +func SOCKS5(network, addr string, auth *Auth, forward Dialer) (Dialer, error) { + s := &socks5{ + network: network, + addr: addr, + forward: forward, + } + if auth != nil { + s.user = auth.User + s.password = auth.Password + } + + return s, nil +} + +type socks5 struct { + user, password string + network, addr string + forward Dialer +} + +const socks5Version = 5 + +const ( + socks5AuthNone = 0 + socks5AuthPassword = 2 +) + +const socks5Connect = 1 + +const ( + socks5IP4 = 1 + socks5Domain = 3 + socks5IP6 = 4 +) + +var socks5Errors = []string{ + "", + "general failure", + "connection forbidden", + "network unreachable", + "host unreachable", + "connection refused", + "TTL expired", + "command not supported", + "address type not supported", +} + +// Dial connects to the address addr on the network net via the SOCKS5 proxy. +func (s *socks5) Dial(network, addr string) (net.Conn, error) { + switch network { + case "tcp", "tcp6", "tcp4": + default: + return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network) + } + + conn, err := s.forward.Dial(s.network, s.addr) + if err != nil { + return nil, err + } + closeConn := &conn + defer func() { + if closeConn != nil { + (*closeConn).Close() + } + }() + + host, portStr, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + + port, err := strconv.Atoi(portStr) + if err != nil { + return nil, errors.New("proxy: failed to parse port number: " + portStr) + } + if port < 1 || port > 0xffff { + return nil, errors.New("proxy: port number out of range: " + portStr) + } + + // the size here is just an estimate + buf := make([]byte, 0, 6+len(host)) + + buf = append(buf, socks5Version) + if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 { + buf = append(buf, 2 /* num auth methods */, socks5AuthNone, socks5AuthPassword) + } else { + buf = append(buf, 1 /* num auth methods */, socks5AuthNone) + } + + if _, err := conn.Write(buf); err != nil { + return nil, errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + if _, err := io.ReadFull(conn, buf[:2]); err != nil { + return nil, errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + if buf[0] != 5 { + return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0]))) + } + if buf[1] == 0xff { + return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication") + } + + if buf[1] == socks5AuthPassword { + buf = buf[:0] + buf = append(buf, 1 /* password protocol version */) + buf = append(buf, uint8(len(s.user))) + buf = append(buf, s.user...) + buf = append(buf, uint8(len(s.password))) + buf = append(buf, s.password...) + + if _, err := conn.Write(buf); err != nil { + return nil, errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + if _, err := io.ReadFull(conn, buf[:2]); err != nil { + return nil, errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + if buf[1] != 0 { + return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password") + } + } + + buf = buf[:0] + buf = append(buf, socks5Version, socks5Connect, 0 /* reserved */) + + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + buf = append(buf, socks5IP4) + ip = ip4 + } else { + buf = append(buf, socks5IP6) + } + buf = append(buf, ip...) + } else { + if len(host) > 255 { + return nil, errors.New("proxy: destination hostname too long: " + host) + } + buf = append(buf, socks5Domain) + buf = append(buf, byte(len(host))) + buf = append(buf, host...) + } + buf = append(buf, byte(port>>8), byte(port)) + + if _, err := conn.Write(buf); err != nil { + return nil, errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + if _, err := io.ReadFull(conn, buf[:4]); err != nil { + return nil, errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + failure := "unknown error" + if int(buf[1]) < len(socks5Errors) { + failure = socks5Errors[buf[1]] + } + + if len(failure) > 0 { + return nil, errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure) + } + + bytesToDiscard := 0 + switch buf[3] { + case socks5IP4: + bytesToDiscard = net.IPv4len + case socks5IP6: + bytesToDiscard = net.IPv6len + case socks5Domain: + _, err := io.ReadFull(conn, buf[:1]) + if err != nil { + return nil, errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + bytesToDiscard = int(buf[0]) + default: + return nil, errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr) + } + + if cap(buf) < bytesToDiscard { + buf = make([]byte, bytesToDiscard) + } else { + buf = buf[:bytesToDiscard] + } + if _, err := io.ReadFull(conn, buf); err != nil { + return nil, errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + // Also need to discard the port number + if _, err := io.ReadFull(conn, buf[:2]); err != nil { + return nil, errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + + closeConn = nil + return conn, nil +} From abbcd1f43662abdc55ca30acf003ca4c06d6f1c0 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Tue, 13 Oct 2015 19:52:22 +0100 Subject: [PATCH 3/4] Patch up HTTP clients --- cmd/syncthing/main.go | 3 +++ lib/dialer/dialer.go | 7 +++++++ lib/discover/global.go | 5 +++++ lib/rc/rc.go | 3 +++ lib/upgrade/upgrade_supported.go | 3 +++ 5 files changed, 21 insertions(+) diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index ff707b8b..fcf4c7fe 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -30,6 +30,7 @@ import ( "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/connections" "github.com/syncthing/syncthing/lib/db" + "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/discover" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/logger" @@ -432,6 +433,8 @@ func upgradeViaRest() error { r.Header.Set("X-API-Key", cfg.GUI().APIKey) tr := &http.Transport{ + Dial: dialer.Dial, + Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client := &http.Client{ diff --git a/lib/dialer/dialer.go b/lib/dialer/dialer.go index 7d9d5b43..584a2682 100644 --- a/lib/dialer/dialer.go +++ b/lib/dialer/dialer.go @@ -8,6 +8,7 @@ package dialer import ( "net" + "net/http" "os" "strings" "time" @@ -26,6 +27,12 @@ var ( func init() { l.SetDebug("dialer", strings.Contains(os.Getenv("STTRACE"), "dialer") || os.Getenv("STTRACE") == "all") if usingProxy { + http.DefaultTransport = &http.Transport{ + Dial: Dial, + Proxy: http.ProxyFromEnvironment, + TLSHandshakeTimeout: 10 * time.Second, + } + // Defer this, so that logging gets setup. go func() { time.Sleep(500 * time.Millisecond) diff --git a/lib/discover/global.go b/lib/discover/global.go index f868583e..d16acc14 100644 --- a/lib/discover/global.go +++ b/lib/discover/global.go @@ -18,6 +18,7 @@ import ( stdsync "sync" "time" + "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/protocol" ) @@ -73,6 +74,8 @@ func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, rela // certificate depending on the insecure setting. var announceClient httpClient = &http.Client{ Transport: &http.Transport{ + Dial: dialer.Dial, + Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{ InsecureSkipVerify: opts.insecure, Certificates: []tls.Certificate{cert}, @@ -87,6 +90,8 @@ func NewGlobal(server string, cert tls.Certificate, addrList AddressLister, rela // certificate here, so lets not include it. May be insecure if requested. var queryClient httpClient = &http.Client{ Transport: &http.Transport{ + Dial: dialer.Dial, + Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{ InsecureSkipVerify: opts.insecure, }, diff --git a/lib/rc/rc.go b/lib/rc/rc.go index 6e7c45a7..1b04e904 100644 --- a/lib/rc/rc.go +++ b/lib/rc/rc.go @@ -26,6 +26,7 @@ import ( "time" "github.com/syncthing/syncthing/lib/config" + "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/sync" ) @@ -156,6 +157,8 @@ func (p *Process) Get(path string) ([]byte, error) { client := &http.Client{ Timeout: 30 * time.Second, Transport: &http.Transport{ + Dial: dialer.Dial, + Proxy: http.ProxyFromEnvironment, DisableKeepAlives: true, }, } diff --git a/lib/upgrade/upgrade_supported.go b/lib/upgrade/upgrade_supported.go index c6651bb9..4dbad86d 100644 --- a/lib/upgrade/upgrade_supported.go +++ b/lib/upgrade/upgrade_supported.go @@ -26,6 +26,7 @@ import ( "sort" "strings" + "github.com/syncthing/syncthing/lib/dialer" "github.com/syncthing/syncthing/lib/signature" ) @@ -38,6 +39,8 @@ const DisabledByCompilation = false // binary contents before accepting the upgrade. var insecureHTTP = &http.Client{ Transport: &http.Transport{ + Dial: dialer.Dial, + Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, From e4e3c19e96b7b9b8c700b8fc676628e118cee5e5 Mon Sep 17 00:00:00 2001 From: Audrius Butkevicius Date: Fri, 16 Oct 2015 19:18:22 +0100 Subject: [PATCH 4/4] Our dialer sets up TCP options --- lib/connections/connections.go | 6 ------ lib/connections/connections_tcp.go | 5 ----- lib/dialer/dialer.go | 7 +++++++ lib/relay/relay.go | 6 ------ 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/lib/connections/connections.go b/lib/connections/connections.go index 525f0832..08e148cb 100644 --- a/lib/connections/connections.go +++ b/lib/connections/connections.go @@ -20,7 +20,6 @@ import ( "github.com/syncthing/syncthing/lib/discover" "github.com/syncthing/syncthing/lib/events" "github.com/syncthing/syncthing/lib/model" - "github.com/syncthing/syncthing/lib/osutil" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/relay" "github.com/syncthing/syncthing/lib/relay/client" @@ -368,11 +367,6 @@ func (s *connectionSvc) connect() { l.Debugln("Sucessfully joined relay session", inv) } - err = osutil.SetTCPOptions(conn.(*net.TCPConn)) - if err != nil { - l.Infoln(err) - } - var tc *tls.Conn if inv.ServerSocket { diff --git a/lib/connections/connections_tcp.go b/lib/connections/connections_tcp.go index 5043c81d..e2ce1cb4 100644 --- a/lib/connections/connections_tcp.go +++ b/lib/connections/connections_tcp.go @@ -44,11 +44,6 @@ func tcpDialer(uri *url.URL, tlsCfg *tls.Config) (*tls.Conn, error) { return nil, err } - err = osutil.SetTCPOptions(conn.(*net.TCPConn)) - if err != nil { - l.Infoln(err) - } - tc := tls.Client(conn, tlsCfg) err = tc.Handshake() if err != nil { diff --git a/lib/dialer/dialer.go b/lib/dialer/dialer.go index 584a2682..5294b7da 100644 --- a/lib/dialer/dialer.go +++ b/lib/dialer/dialer.go @@ -16,6 +16,7 @@ import ( "golang.org/x/net/proxy" "github.com/syncthing/syncthing/lib/logger" + "github.com/syncthing/syncthing/lib/osutil" ) var ( @@ -48,6 +49,9 @@ func Dial(network, addr string) (net.Conn, error) { conn, err := dialer.Dial(network, addr) if err == nil { l.Debugf("Dialing %s address %s via proxy - success, %s -> %s", network, addr, conn.LocalAddr(), conn.RemoteAddr()) + if tcpconn, ok := conn.(*net.TCPConn); ok { + osutil.SetTCPOptions(tcpconn) + } return dialerConn{ conn, newDialerAddr(network, addr), }, nil @@ -58,6 +62,9 @@ func Dial(network, addr string) (net.Conn, error) { conn, err := proxy.Direct.Dial(network, addr) if err == nil { l.Debugf("Dialing %s address %s directly - success, %s -> %s", network, addr, conn.LocalAddr(), conn.RemoteAddr()) + if tcpconn, ok := conn.(*net.TCPConn); ok { + osutil.SetTCPOptions(tcpconn) + } } else { l.Debugf("Dialing %s address %s directly - error %s", network, addr, err) } diff --git a/lib/relay/relay.go b/lib/relay/relay.go index a808d4cc..04e970a6 100644 --- a/lib/relay/relay.go +++ b/lib/relay/relay.go @@ -9,7 +9,6 @@ package relay import ( "crypto/tls" "encoding/json" - "net" "net/http" "net/url" "sort" @@ -249,11 +248,6 @@ func (r *invitationReceiver) Serve() { continue } - err = osutil.SetTCPOptions(conn.(*net.TCPConn)) - if err != nil { - l.Infoln(err) - } - var tc *tls.Conn if inv.ServerSocket {