Merge pull request 'Allow Custom Tor Config in TorRCBuilder + ProxyACN and ErrorACN' (#25) from custom_tor_config into master
Reviewed-on: #25
This commit is contained in:
commit
85da1dac37
|
@ -10,10 +10,10 @@ pipeline:
|
|||
event: [ push, pull_request ]
|
||||
image: golang
|
||||
commands:
|
||||
- go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/tor -P tmp/
|
||||
- chmod a+x tmp/tor
|
||||
- go mod download
|
||||
- go get -u golang.org/x/lint/golint
|
||||
quality:
|
||||
when:
|
||||
repo: openprivacy/connectivity
|
||||
|
@ -21,8 +21,7 @@ pipeline:
|
|||
event: [ push, pull_request ]
|
||||
image: golang
|
||||
commands:
|
||||
- go list ./... | xargs go vet
|
||||
- go list ./... | xargs golint -set_exit_status
|
||||
- staticcheck ./...
|
||||
units-tests:
|
||||
when:
|
||||
repo: openprivacy/connectivity
|
||||
|
|
|
@ -4,3 +4,4 @@ tor/tor/
|
|||
vendor/
|
||||
*.cover.out
|
||||
tmp/
|
||||
testing/tor/*
|
7
acn.go
7
acn.go
|
@ -21,9 +21,6 @@ type PrivateKey interface{}
|
|||
|
||||
// ListenService is an address that was opened with Listen() and can Accept() new connections
|
||||
type ListenService interface {
|
||||
// AddressIdentity is the core "identity" part of an address, ex: rsjeuxzlexy4fvo75vrdtj37nrvlmvbw57n5mhypcjpzv3xkka3l4yyd
|
||||
AddressIdentity() string
|
||||
|
||||
// AddressFull is the full network address, ex: rsjeuxzlexy4fvo75vrdtj37nrvlmvbw57n5mhypcjpzv3xkka3l4yyd.onion:9878
|
||||
AddressFull() string
|
||||
|
||||
|
@ -38,7 +35,7 @@ type ACN interface {
|
|||
// On ACN error state it returns -2
|
||||
GetBootstrapStatus() (int, string)
|
||||
// WaitTillBootstrapped Blocks until underlying network is bootstrapped
|
||||
WaitTillBootstrapped()
|
||||
WaitTillBootstrapped() error
|
||||
// Sets the callback function to be called when ACN status changes
|
||||
SetStatusCallback(callback func(int, string))
|
||||
|
||||
|
@ -58,5 +55,7 @@ type ACN interface {
|
|||
// GetVersion returns a string of what the ACN returns when asked for a version
|
||||
GetVersion() string
|
||||
|
||||
Callback() func(int, string)
|
||||
|
||||
Close()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
package connectivity
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// ErrorACN - a status-callback safe errored ACN. Use this when ACN construction goes wrong
|
||||
// and you need a safe substitute that can later be replaced with a working ACN without impacting calling clients.
|
||||
type ErrorACN struct {
|
||||
statusCallbackCache func(int, string)
|
||||
}
|
||||
|
||||
func (e ErrorACN) Callback() func(int, string) {
|
||||
return e.statusCallbackCache
|
||||
}
|
||||
|
||||
func (e ErrorACN) GetBootstrapStatus() (int, string) {
|
||||
return -1, "error initializing tor"
|
||||
}
|
||||
|
||||
func (e ErrorACN) WaitTillBootstrapped() error {
|
||||
return errors.New("error initializing tor")
|
||||
}
|
||||
|
||||
func (e *ErrorACN) SetStatusCallback(callback func(int, string)) {
|
||||
e.statusCallbackCache = callback
|
||||
}
|
||||
|
||||
func (e ErrorACN) Restart() {
|
||||
}
|
||||
|
||||
func (e ErrorACN) Open(hostname string) (net.Conn, string, error) {
|
||||
return nil, "", fmt.Errorf("error initializing tor")
|
||||
}
|
||||
|
||||
func (e ErrorACN) Listen(identity PrivateKey, port int) (ListenService, error) {
|
||||
return nil, fmt.Errorf("error initializing tor")
|
||||
}
|
||||
|
||||
func (e ErrorACN) GetPID() (int, error) {
|
||||
return -1, fmt.Errorf("error initializing tor")
|
||||
}
|
||||
|
||||
func (e ErrorACN) GetVersion() string {
|
||||
return "Error Initializing Tor"
|
||||
}
|
||||
|
||||
func (e ErrorACN) Close() {
|
||||
}
|
|
@ -18,6 +18,10 @@ func NewLocalACN() ACN {
|
|||
return &localProvider{}
|
||||
}
|
||||
|
||||
func (lp *localProvider) Callback() func(int, string) {
|
||||
return func(int, string) {}
|
||||
}
|
||||
|
||||
func (ls *localListenService) AddressFull() string {
|
||||
return ls.l.Addr().String()
|
||||
}
|
||||
|
@ -52,7 +56,8 @@ func (lp *localProvider) GetVersion() string {
|
|||
}
|
||||
|
||||
// WaitTillBootstrapped Blocks until underlying network is bootstrapped
|
||||
func (lp *localProvider) WaitTillBootstrapped() {
|
||||
func (lp *localProvider) WaitTillBootstrapped() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lp *localProvider) Listen(identity PrivateKey, port int) (ListenService, error) {
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package connectivity
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ProxyACN because there is rarely a problem that can't be solved by another layer of indirection.
|
||||
// ACN is a core resource that many parts of a system may need access too e.g. all clients and servers need an instance
|
||||
// and a UI may also need status information and a configuration interface.
|
||||
// We want to allow configuration and replacement of an ACN without impacting the API of all downstream systems - introducing
|
||||
// ProxyACN - a wrapper around an ACN that allows safe replacement of a running ACN that is transparent to callers.
|
||||
type ProxyACN struct {
|
||||
acn ACN
|
||||
|
||||
// All operations on the underlying acn are assumed to be thread safe, however changing the actual
|
||||
// acn in ReplaceACN will lock to force an ordering of Close and Callback
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewProxyACN(acn ACN) ProxyACN {
|
||||
return ProxyACN{
|
||||
acn: acn,
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceACN closes down the current ACN and replaces it with a new ACN.
|
||||
func (p *ProxyACN) ReplaceACN(acn ACN) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
p.acn.Close()
|
||||
acn.SetStatusCallback(p.acn.Callback())
|
||||
p.acn = acn
|
||||
}
|
||||
|
||||
func (p *ProxyACN) GetBootstrapStatus() (int, string) {
|
||||
return p.acn.GetBootstrapStatus()
|
||||
}
|
||||
|
||||
func (p *ProxyACN) WaitTillBootstrapped() error {
|
||||
return p.acn.WaitTillBootstrapped()
|
||||
}
|
||||
|
||||
func (p *ProxyACN) SetStatusCallback(callback func(int, string)) {
|
||||
p.acn.SetStatusCallback(callback)
|
||||
}
|
||||
|
||||
func (p *ProxyACN) Restart() {
|
||||
p.acn.Restart()
|
||||
}
|
||||
|
||||
func (p *ProxyACN) Open(hostname string) (net.Conn, string, error) {
|
||||
return p.acn.Open(hostname)
|
||||
}
|
||||
|
||||
func (p *ProxyACN) Listen(identity PrivateKey, port int) (ListenService, error) {
|
||||
return p.acn.Listen(identity, port)
|
||||
}
|
||||
|
||||
func (p *ProxyACN) GetPID() (int, error) {
|
||||
return p.acn.GetPID()
|
||||
}
|
||||
|
||||
func (p *ProxyACN) GetVersion() string {
|
||||
return p.acn.GetVersion()
|
||||
}
|
||||
|
||||
func (p *ProxyACN) Close() {
|
||||
p.acn.Close()
|
||||
}
|
||||
|
||||
func (p *ProxyACN) Callback() func(int, string) {
|
||||
return p.acn.Callback()
|
||||
}
|
|
@ -32,7 +32,11 @@ func TestLaunchTor(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("tor failed to start: %v", err)
|
||||
} else {
|
||||
acn.WaitTillBootstrapped()
|
||||
err := acn.WaitTillBootstrapped()
|
||||
if err != nil {
|
||||
t.Fatalf("error bootstrapping tor %v", err)
|
||||
}
|
||||
|
||||
if pid, err := acn.GetPID(); err == nil {
|
||||
t.Logf("tor pid: %v", pid)
|
||||
} else {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Checking code quality (you want to see no output here)"
|
||||
|
||||
echo "Formatting:"
|
||||
gofmt -s -w -l .
|
||||
echo ""
|
||||
|
||||
echo "Vetting:"
|
||||
go list ./... | xargs go vet
|
||||
|
@ -11,12 +9,16 @@ go list ./... | xargs go vet
|
|||
echo ""
|
||||
echo "Linting:"
|
||||
|
||||
go list ./... | xargs golint
|
||||
staticcheck ./...
|
||||
|
||||
|
||||
echo "Time to format"
|
||||
gofmt -l -s -w .
|
||||
|
||||
# ineffassign (https://github.com/gordonklaus/ineffassign)
|
||||
echo "Checking for ineffectual assignment of errors (unchecked errors...)"
|
||||
ineffassign .
|
||||
|
||||
# misspell (https://github.com/client9/misspell)
|
||||
# misspell (https://github.com/client9/misspell/cmd/misspell)
|
||||
echo "Checking for misspelled words..."
|
||||
go list ./... | xargs misspell
|
||||
misspell . | grep -v "testing/" | grep -v "vendor/" | grep -v "go.sum" | grep -v ".idea"
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
SOCKSPort 9050
|
||||
ControlPort 9051
|
||||
# "examplehashedpassword" - used for testing
|
||||
HashedControlPassword 16:C15305F97789414B601259E3EC5E76B8E55FC56A9F562B713F3D2BA257
|
|
@ -55,9 +55,9 @@ func (l *logWriter) Write(p []byte) (int, error) {
|
|||
}
|
||||
|
||||
type onionListenService struct {
|
||||
lock sync.Mutex
|
||||
os *tor.OnionService
|
||||
tp *torProvider
|
||||
lock sync.Mutex
|
||||
os *tor.OnionService
|
||||
tp *torProvider
|
||||
}
|
||||
|
||||
type torProvider struct {
|
||||
|
@ -72,6 +72,7 @@ type torProvider struct {
|
|||
statusCallback func(int, string)
|
||||
lastRestartTime time.Time
|
||||
authenticator tor.Authenticator
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
func (ols *onionListenService) AddressFull() string {
|
||||
|
@ -80,21 +81,13 @@ func (ols *onionListenService) AddressFull() string {
|
|||
return ols.os.Addr().String()
|
||||
}
|
||||
|
||||
func (ols *onionListenService) AddressIdentity() string {
|
||||
ols.lock.Lock()
|
||||
defer ols.lock.Unlock()
|
||||
return ols.os.Addr().String()[:56]
|
||||
}
|
||||
|
||||
func (ols *onionListenService) Accept() (net.Conn, error) {
|
||||
return ols.os.Accept()
|
||||
}
|
||||
|
||||
func (ols *onionListenService) Close() {
|
||||
address := ols.AddressIdentity()
|
||||
ols.lock.Lock()
|
||||
defer ols.lock.Unlock()
|
||||
ols.tp.unregisterListener(address)
|
||||
ols.os.Close()
|
||||
}
|
||||
|
||||
|
@ -156,21 +149,25 @@ func (tp *torProvider) GetVersion() string {
|
|||
return "No Tor"
|
||||
}
|
||||
|
||||
func (tp *torProvider) closed() bool {
|
||||
tp.lock.Lock()
|
||||
defer tp.lock.Unlock()
|
||||
return tp.isClosed
|
||||
}
|
||||
|
||||
// WaitTillBootstrapped Blocks until underlying network is bootstrapped
|
||||
func (tp *torProvider) WaitTillBootstrapped() {
|
||||
for {
|
||||
func (tp *torProvider) WaitTillBootstrapped() error {
|
||||
for !tp.closed() {
|
||||
progress, _ := tp.GetBootstrapStatus()
|
||||
if progress == 100 {
|
||||
break
|
||||
return nil
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return errors.New("close called before bootstrap")
|
||||
}
|
||||
|
||||
func (tp *torProvider) Listen(identity connectivity.PrivateKey, port int) (connectivity.ListenService, error) {
|
||||
var onion = ""
|
||||
var privkey ed25519.PrivateKey
|
||||
|
||||
tp.lock.Lock()
|
||||
defer tp.lock.Unlock()
|
||||
|
||||
|
@ -178,6 +175,9 @@ func (tp *torProvider) Listen(identity connectivity.PrivateKey, port int) (conne
|
|||
return nil, errors.New("tor provider closed")
|
||||
}
|
||||
|
||||
var onion string
|
||||
var privkey ed25519.PrivateKey
|
||||
|
||||
switch pk := identity.(type) {
|
||||
case ed25519.PrivateKey:
|
||||
privkey = pk
|
||||
|
@ -185,7 +185,11 @@ func (tp *torProvider) Listen(identity connectivity.PrivateKey, port int) (conne
|
|||
switch pubk := gpubk.(type) {
|
||||
case ed25519.PublicKey:
|
||||
onion = GetTorV3Hostname(pubk)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown public key type %v", pubk)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown private key type %v", pk)
|
||||
}
|
||||
|
||||
// Hack around tor detached onions not having a more obvious resume mechanism
|
||||
|
@ -221,7 +225,7 @@ func (tp *torProvider) Listen(identity connectivity.PrivateKey, port int) (conne
|
|||
os.CloseLocalListenerOnClose = true
|
||||
|
||||
ols := &onionListenService{os: os, tp: tp}
|
||||
tp.childListeners[ols.AddressIdentity()] = ols
|
||||
tp.childListeners[ols.AddressFull()] = ols
|
||||
return ols, nil
|
||||
}
|
||||
|
||||
|
@ -284,16 +288,24 @@ func (tp *torProvider) Open(hostname string) (net.Conn, string, error) {
|
|||
}
|
||||
|
||||
func (tp *torProvider) Close() {
|
||||
for _, child := range tp.childListeners {
|
||||
child.Close()
|
||||
}
|
||||
|
||||
tp.lock.Lock()
|
||||
defer tp.lock.Unlock()
|
||||
tp.breakChan <- true
|
||||
if tp.t != nil {
|
||||
tp.t.Close()
|
||||
tp.t = nil
|
||||
|
||||
// Unregister Child Listeners
|
||||
for addr, child := range tp.childListeners {
|
||||
child.Close()
|
||||
delete(tp.childListeners, addr)
|
||||
}
|
||||
|
||||
if !tp.isClosed {
|
||||
// Break out of any background checks and close
|
||||
// the underlying tor connection
|
||||
tp.isClosed = true
|
||||
tp.breakChan <- true
|
||||
if tp.t != nil {
|
||||
tp.t.Close()
|
||||
tp.t = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -303,6 +315,12 @@ func (tp *torProvider) SetStatusCallback(callback func(int, string)) {
|
|||
tp.statusCallback = callback
|
||||
}
|
||||
|
||||
func (tp *torProvider) Callback() func(int, string) {
|
||||
tp.lock.Lock()
|
||||
defer tp.lock.Unlock()
|
||||
return tp.statusCallback
|
||||
}
|
||||
|
||||
func (tp *torProvider) callStatusCallback(prog int, status string) {
|
||||
tp.lock.Lock()
|
||||
if tp.statusCallback != nil {
|
||||
|
@ -410,7 +428,7 @@ func startTor(appDirectory string, bundledTorPath string, controlPort int, authe
|
|||
tp.t = t
|
||||
} else {
|
||||
log.Debugln("Could not find a viable tor running or to run")
|
||||
return nil, fmt.Errorf("Could not connect to or start Tor that met requirements (min Tor version 0.3.5.x)")
|
||||
return nil, fmt.Errorf("could not connect to or start Tor that met requirements (min Tor version 0.3.5.x)")
|
||||
}
|
||||
|
||||
err = tp.checkVersion()
|
||||
|
@ -430,12 +448,6 @@ func (tp *torProvider) GetPID() (int, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
func (tp *torProvider) unregisterListener(id string) {
|
||||
tp.lock.Lock()
|
||||
defer tp.lock.Unlock()
|
||||
delete(tp.childListeners, id)
|
||||
}
|
||||
|
||||
func (tp *torProvider) monitorRestart() {
|
||||
lastBootstrapProgress := networkUnknown
|
||||
interval := minStatusIntervalMs
|
||||
|
|
|
@ -3,7 +3,7 @@ package tor
|
|||
import (
|
||||
"fmt"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"path"
|
||||
path "path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -18,6 +18,9 @@ func TestTorProvider(t *testing.T) {
|
|||
progChan := make(chan int)
|
||||
log.SetLevel(log.LevelDebug)
|
||||
torpath := path.Join("..", "tmp/tor")
|
||||
|
||||
NewTorrc().WithControlPort(9051).WithHashedPassword("examplehashedpassword").Build(path.Join("..", "testing", "tor", "torrc"))
|
||||
|
||||
log.Debugf("setting tor path %v", torpath)
|
||||
acn, err := NewTorACNWithAuth(path.Join("../testing/"), torpath, 9051, HashedPasswordAuthenticator{"examplehashedpassword"})
|
||||
if err != nil {
|
||||
|
|
|
@ -98,3 +98,11 @@ func TestGenerateTorrc(t *testing.T) {
|
|||
}
|
||||
os.Remove(path)
|
||||
}
|
||||
|
||||
func TestPreviewTorrc(t *testing.T) {
|
||||
expected := "SocksPort 9050 OnionTrafficOnly\nControlPort 9061"
|
||||
torrc := NewTorrc().WithCustom([]string{"SocksPort 9050"}).WithControlPort(9061).WithOnionTrafficOnly().Preview()
|
||||
if torrc != expected {
|
||||
t.Fatalf("unexpected torrc generated: [%v] [%v]", expected, torrc)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,13 @@ func (tb *TorrcBuilder) WithControlPort(port int) *TorrcBuilder {
|
|||
return tb
|
||||
}
|
||||
|
||||
// WithCustom clobbers the torrc builder and allows the client to set any option they want, while benefiting
|
||||
// from other configuration options.
|
||||
func (tb *TorrcBuilder) WithCustom(lines []string) *TorrcBuilder {
|
||||
tb.lines = lines
|
||||
return tb
|
||||
}
|
||||
|
||||
// WithOnionTrafficOnly ensures that the tor process only routes tor onion traffic.
|
||||
func (tb *TorrcBuilder) WithOnionTrafficOnly() *TorrcBuilder {
|
||||
for i, line := range tb.lines {
|
||||
|
@ -61,6 +68,11 @@ func (tb *TorrcBuilder) Build(path string) error {
|
|||
return ioutil.WriteFile(path, []byte(strings.Join(tb.lines, "\n")), 0600)
|
||||
}
|
||||
|
||||
// Preview provides a string representation of the torrc file without writing it to a file location.
|
||||
func (tb *TorrcBuilder) Preview() string {
|
||||
return strings.Join(tb.lines, "\n")
|
||||
}
|
||||
|
||||
// GenerateHashedPassword calculates a hash in the same way tha tor --hash-password does
|
||||
// this function takes a salt as input which is not great from an api-misuse perspective, but
|
||||
// we make it private.
|
||||
|
|
Loading…
Reference in New Issue