From fc0c6b3c95e2a1e2cd01e09eee968fb7d719f6eb Mon Sep 17 00:00:00 2001 From: John Brooks Date: Thu, 29 Sep 2016 22:11:37 -0700 Subject: [PATCH] core: Add OnionConnector for outbound connections --- core/onionconnector.go | 86 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 core/onionconnector.go diff --git a/core/onionconnector.go b/core/onionconnector.go new file mode 100644 index 0000000..0d2ebe8 --- /dev/null +++ b/core/onionconnector.go @@ -0,0 +1,86 @@ +package core + +import ( + "errors" + "golang.org/x/net/context" + "log" + "net" + "strings" + "time" +) + +// XXX on failures, decide how long to wait before trying again +// XXX limit and queue global attempts? +// XXX circ/stream/fetch monitoring + +type OnionConnector struct { + Network *Network + NeverGiveUp bool + AttemptCount int +} + +// Attempt to connect to 'address', which must be a .onion address and port, +// using the Network from this OnionConnector instance. +// +// If NeverGiveUp is set, failed connections will be retried automatically, +// with appropriate backoff periods, and an error is only returned in fatal +// situations. The backoff is defined by AttemptCount on the OnionConnector +// instance -- it is not reset after Connect returns. +// +// If the Network is not ready, this function will wait and monitor the +// Network's status. +// +// Context can be used to set a timeout, deadline, or cancel function for +// the connection attempt. +func (oc *OnionConnector) Connect(address string, c context.Context) (net.Conn, error) { + if oc.Network == nil { + return nil, errors.New("No network configured for connector") + } + + host, _, err := net.SplitHostPort(address) + if err != nil || !strings.HasSuffix(host, ".onion") { + return nil, errors.New("Invalid address") + } + + options := &net.Dialer{ + Cancel: c.Done(), + Timeout: 0, // XXX should use timeout + } + + for { + // XXX This waits to know SOCKS info, but does not wait for connection + // ready state; should it? + proxy, err := oc.Network.WaitForProxyDialer(options, c) + if err != nil { + return nil, err + } + + conn, err := proxy.Dial("tcp", address) + if err != nil { + if c.Err() != nil { + return nil, c.Err() + } + if !oc.NeverGiveUp { + return nil, err + } + + log.Printf("Connection attempt to %s failed: %s", address, err) + if err := oc.Backoff(c); err != nil { + return nil, err + } + + continue + } + + return conn, nil + } +} + +func (oc *OnionConnector) Backoff(c context.Context) error { + oc.AttemptCount++ + // XXX This should actually do backoff and be less dumb + waitCtx, finish := context.WithTimeout(c, 10*time.Second) + defer finish() + <-waitCtx.Done() + return c.Err() +}