Merge pull request #125 from vdemeester/expose-through-tcp
Allow to use TCP for buildkit daemon and client communicationdocker-18.09
commit
537b5e459d
|
@ -29,9 +29,9 @@ func New(address string, opts ...ClientOpt) (*Client, error) {
|
|||
}
|
||||
}
|
||||
if address == "" {
|
||||
address = appdefaults.Socket
|
||||
address = appdefaults.Address
|
||||
}
|
||||
conn, err := grpc.Dial(dialAddress(address), gopts...)
|
||||
conn, err := grpc.Dial(address, gopts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to dial %q . make sure buildd is running", address)
|
||||
}
|
||||
|
|
|
@ -50,9 +50,11 @@ func runBuildd(args []string) (string, func(), error) {
|
|||
address := filepath.Join(tmpdir, "buildd.sock")
|
||||
if runtime.GOOS == "windows" {
|
||||
address = "//./pipe/buildd-" + filepath.Base(tmpdir)
|
||||
} else {
|
||||
address = "unix://" + address
|
||||
}
|
||||
|
||||
args = append(args, "--root", tmpdir, "--socket", address, "--debug")
|
||||
args = append(args, "--root", tmpdir, "--addr", address, "--debug")
|
||||
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
// cmd.Stderr = os.Stdout
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func dialer(address string, timeout time.Duration) (net.Conn, error) {
|
||||
address = strings.TrimPrefix(address, "unix://")
|
||||
return net.DialTimeout("unix", address, timeout)
|
||||
}
|
||||
|
||||
func dialAddress(address string) string {
|
||||
return fmt.Sprintf("unix://%s", address)
|
||||
addrParts := strings.SplitN(address, "://", 2)
|
||||
if len(addrParts) != 2 {
|
||||
return nil, errors.Errorf("invalid address %s", address)
|
||||
}
|
||||
return net.DialTimeout(addrParts[0], addrParts[1], timeout)
|
||||
}
|
||||
|
|
|
@ -22,9 +22,9 @@ func main() {
|
|||
Usage: "enable debug output in logs",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "socket",
|
||||
Usage: "listening socket",
|
||||
Value: appdefaults.Socket,
|
||||
Name: "addr",
|
||||
Usage: "listening address",
|
||||
Value: appdefaults.Address,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -50,5 +50,5 @@ func main() {
|
|||
}
|
||||
|
||||
func resolveClient(c *cli.Context) (*client.Client, error) {
|
||||
return client.New(c.GlobalString("socket"), client.WithBlock())
|
||||
return client.New(c.GlobalString("addr"), client.WithBlock())
|
||||
}
|
||||
|
|
|
@ -2,10 +2,13 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/sys"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/moby/buildkit/util/appdefaults"
|
||||
"github.com/moby/buildkit/util/profiler"
|
||||
|
@ -32,13 +35,13 @@ func main() {
|
|||
Value: appdefaults.Root,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "socket",
|
||||
Usage: "listening socket",
|
||||
Value: appdefaults.Socket,
|
||||
Name: "addr",
|
||||
Usage: "listening address (socket or tcp)",
|
||||
Value: appdefaults.Address,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "debugaddr",
|
||||
Usage: "Debugging address (eg. 0.0.0.0:6060)",
|
||||
Usage: "debugging address (eg. 0.0.0.0:6060)",
|
||||
Value: "",
|
||||
},
|
||||
}
|
||||
|
@ -74,7 +77,7 @@ func main() {
|
|||
controller.Register(server)
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
if err := serveGRPC(server, c.GlobalString("socket"), errCh); err != nil {
|
||||
if err := serveGRPC(server, c.GlobalString("addr"), errCh); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -106,22 +109,36 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
func serveGRPC(server *grpc.Server, path string, errCh chan error) error {
|
||||
if path == "" {
|
||||
return errors.New("--socket path cannot be empty")
|
||||
func serveGRPC(server *grpc.Server, addr string, errCh chan error) error {
|
||||
if addr == "" {
|
||||
return errors.New("--addr cannot be empty")
|
||||
}
|
||||
l, err := sys.GetLocalListener(path, os.Getuid(), os.Getgid())
|
||||
l, err := getListener(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
defer l.Close()
|
||||
logrus.Infof("running server on %s", path)
|
||||
logrus.Infof("running server on %s", addr)
|
||||
errCh <- server.Serve(l)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func getListener(addr string) (net.Listener, error) {
|
||||
addrSlice := strings.SplitN(addr, "://", 2)
|
||||
proto := addrSlice[0]
|
||||
listenAddr := addrSlice[1]
|
||||
switch proto {
|
||||
case "unix", "npipe":
|
||||
return sys.GetLocalListener(listenAddr, os.Getuid(), os.Getgid())
|
||||
case "tcp":
|
||||
return sockets.NewTCPSocket(listenAddr, nil)
|
||||
default:
|
||||
return nil, errors.Errorf("addr %s not supported", addr)
|
||||
}
|
||||
}
|
||||
|
||||
func unaryInterceptor(globalCtx context.Context) grpc.ServerOption {
|
||||
return grpc.UnaryInterceptor(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
package appdefaults
|
||||
|
||||
const (
|
||||
Socket = "/run/buildkit/buildd.sock"
|
||||
Root = ".buildstate"
|
||||
Address = "unix:///run/buildkit/buildd.sock"
|
||||
Root = ".buildstate"
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package appdefaults
|
||||
|
||||
const (
|
||||
Socket = "//./pipe/buildd"
|
||||
Root = ".buildstate"
|
||||
Address = "npipe:////./pipe/buildd"
|
||||
Root = ".buildstate"
|
||||
)
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package sockets
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var errClosed = errors.New("use of closed network connection")
|
||||
|
||||
// InmemSocket implements net.Listener using in-memory only connections.
|
||||
type InmemSocket struct {
|
||||
chConn chan net.Conn
|
||||
chClose chan struct{}
|
||||
addr string
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// dummyAddr is used to satisfy net.Addr for the in-mem socket
|
||||
// it is just stored as a string and returns the string for all calls
|
||||
type dummyAddr string
|
||||
|
||||
// NewInmemSocket creates an in-memory only net.Listener
|
||||
// The addr argument can be any string, but is used to satisfy the `Addr()` part
|
||||
// of the net.Listener interface
|
||||
func NewInmemSocket(addr string, bufSize int) *InmemSocket {
|
||||
return &InmemSocket{
|
||||
chConn: make(chan net.Conn, bufSize),
|
||||
chClose: make(chan struct{}),
|
||||
addr: addr,
|
||||
}
|
||||
}
|
||||
|
||||
// Addr returns the socket's addr string to satisfy net.Listener
|
||||
func (s *InmemSocket) Addr() net.Addr {
|
||||
return dummyAddr(s.addr)
|
||||
}
|
||||
|
||||
// Accept implements the Accept method in the Listener interface; it waits for the next call and returns a generic Conn.
|
||||
func (s *InmemSocket) Accept() (net.Conn, error) {
|
||||
select {
|
||||
case conn := <-s.chConn:
|
||||
return conn, nil
|
||||
case <-s.chClose:
|
||||
return nil, errClosed
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the listener. It will be unavailable for use once closed.
|
||||
func (s *InmemSocket) Close() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
select {
|
||||
case <-s.chClose:
|
||||
default:
|
||||
close(s.chClose)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dial is used to establish a connection with the in-mem server
|
||||
func (s *InmemSocket) Dial(network, addr string) (net.Conn, error) {
|
||||
srvConn, clientConn := net.Pipe()
|
||||
select {
|
||||
case s.chConn <- srvConn:
|
||||
case <-s.chClose:
|
||||
return nil, errClosed
|
||||
}
|
||||
|
||||
return clientConn, nil
|
||||
}
|
||||
|
||||
// Network returns the addr string, satisfies net.Addr
|
||||
func (a dummyAddr) Network() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
// String returns the string form
|
||||
func (a dummyAddr) String() string {
|
||||
return string(a)
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package sockets
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
// GetProxyEnv allows access to the uppercase and the lowercase forms of
|
||||
// proxy-related variables. See the Go specification for details on these
|
||||
// variables. https://golang.org/pkg/net/http/
|
||||
func GetProxyEnv(key string) string {
|
||||
proxyValue := os.Getenv(strings.ToUpper(key))
|
||||
if proxyValue == "" {
|
||||
return os.Getenv(strings.ToLower(key))
|
||||
}
|
||||
return proxyValue
|
||||
}
|
||||
|
||||
// DialerFromEnvironment takes in a "direct" *net.Dialer and returns a
|
||||
// proxy.Dialer which will route the connections through the proxy using the
|
||||
// given dialer.
|
||||
func DialerFromEnvironment(direct *net.Dialer) (proxy.Dialer, error) {
|
||||
allProxy := GetProxyEnv("all_proxy")
|
||||
if len(allProxy) == 0 {
|
||||
return direct, nil
|
||||
}
|
||||
|
||||
proxyURL, err := url.Parse(allProxy)
|
||||
if err != nil {
|
||||
return direct, err
|
||||
}
|
||||
|
||||
proxyFromURL, err := proxy.FromURL(proxyURL, direct)
|
||||
if err != nil {
|
||||
return direct, err
|
||||
}
|
||||
|
||||
noProxy := GetProxyEnv("no_proxy")
|
||||
if len(noProxy) == 0 {
|
||||
return proxyFromURL, nil
|
||||
}
|
||||
|
||||
perHost := proxy.NewPerHost(proxyFromURL, direct)
|
||||
perHost.AddFromString(noProxy)
|
||||
|
||||
return perHost, nil
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Package sockets provides helper functions to create and configure Unix or TCP sockets.
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Why 32? See https://github.com/docker/docker/pull/8035.
|
||||
const defaultTimeout = 32 * time.Second
|
||||
|
||||
// ErrProtocolNotAvailable is returned when a given transport protocol is not provided by the operating system.
|
||||
var ErrProtocolNotAvailable = errors.New("protocol not available")
|
||||
|
||||
// ConfigureTransport configures the specified Transport according to the
|
||||
// specified proto and addr.
|
||||
// If the proto is unix (using a unix socket to communicate) or npipe the
|
||||
// compression is disabled.
|
||||
func ConfigureTransport(tr *http.Transport, proto, addr string) error {
|
||||
switch proto {
|
||||
case "unix":
|
||||
return configureUnixTransport(tr, proto, addr)
|
||||
case "npipe":
|
||||
return configureNpipeTransport(tr, proto, addr)
|
||||
default:
|
||||
tr.Proxy = http.ProxyFromEnvironment
|
||||
dialer, err := DialerFromEnvironment(&net.Dialer{
|
||||
Timeout: defaultTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr.Dial = dialer.Dial
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// +build !windows
|
||||
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path)
|
||||
|
||||
func configureUnixTransport(tr *http.Transport, proto, addr string) error {
|
||||
if len(addr) > maxUnixSocketPathSize {
|
||||
return fmt.Errorf("Unix socket path %q is too long", addr)
|
||||
}
|
||||
// No need for compression in local communications.
|
||||
tr.DisableCompression = true
|
||||
tr.Dial = func(_, _ string) (net.Conn, error) {
|
||||
return net.DialTimeout(proto, addr, defaultTimeout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
|
||||
return ErrProtocolNotAvailable
|
||||
}
|
||||
|
||||
// DialPipe connects to a Windows named pipe.
|
||||
// This is not supported on other OSes.
|
||||
func DialPipe(_ string, _ time.Duration) (net.Conn, error) {
|
||||
return nil, syscall.EAFNOSUPPORT
|
||||
}
|
27
vendor/github.com/docker/go-connections/sockets/sockets_windows.go
generated
vendored
Normal file
27
vendor/github.com/docker/go-connections/sockets/sockets_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
package sockets
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/go-winio"
|
||||
)
|
||||
|
||||
func configureUnixTransport(tr *http.Transport, proto, addr string) error {
|
||||
return ErrProtocolNotAvailable
|
||||
}
|
||||
|
||||
func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
|
||||
// No need for compression in local communications.
|
||||
tr.DisableCompression = true
|
||||
tr.Dial = func(_, _ string) (net.Conn, error) {
|
||||
return DialPipe(addr, defaultTimeout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DialPipe connects to a Windows named pipe.
|
||||
func DialPipe(addr string, timeout time.Duration) (net.Conn, error) {
|
||||
return winio.DialPipe(addr, &timeout)
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Package sockets provides helper functions to create and configure Unix or TCP sockets.
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
)
|
||||
|
||||
// NewTCPSocket creates a TCP socket listener with the specified address and
|
||||
// the specified tls configuration. If TLSConfig is set, will encapsulate the
|
||||
// TCP listener inside a TLS one.
|
||||
func NewTCPSocket(addr string, tlsConfig *tls.Config) (net.Listener, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tlsConfig != nil {
|
||||
tlsConfig.NextProtos = []string{"http/1.1"}
|
||||
l = tls.NewListener(l, tlsConfig)
|
||||
}
|
||||
return l, nil
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// +build !windows
|
||||
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// NewUnixSocket creates a unix socket with the specified path and group.
|
||||
func NewUnixSocket(path string, gid int) (net.Listener, error) {
|
||||
if err := syscall.Unlink(path); err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
mask := syscall.Umask(0777)
|
||||
defer syscall.Umask(mask)
|
||||
|
||||
l, err := net.Listen("unix", path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.Chown(path, 0, gid); err != nil {
|
||||
l.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := os.Chmod(path, 0660); err != nil {
|
||||
l.Close()
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
// 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
|
||||
}
|
||||
if err := s.connect(conn, addr); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// connect takes an existing connection to a socks5 proxy server,
|
||||
// and commands the server to extend that connection to target,
|
||||
// which must be a canonical address with a host and port.
|
||||
func (s *socks5) connect(conn net.Conn, target string) error {
|
||||
host, portStr, err := net.SplitHostPort(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return errors.New("proxy: failed to parse port number: " + portStr)
|
||||
}
|
||||
if port < 1 || port > 0xffff {
|
||||
return 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 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 errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
if buf[0] != 5 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||||
}
|
||||
if buf[1] == 0xff {
|
||||
return 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 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 errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if buf[1] != 0 {
|
||||
return 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 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 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 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 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 errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
bytesToDiscard = int(buf[0])
|
||||
default:
|
||||
return 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 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 errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue