Compare commits

...

35 Commits

Author SHA1 Message Date
Sarah Jamie Lewis 1524e78a4a Merge pull request 'Clarified and Split Apart Environment Variables that alter port binding behaviour.' (#47) from whonix into master
continuous-integration/drone/push Build is pending Details
Reviewed-on: #47
Reviewed-by: Dan Ballard <dan@openprivacy.ca>
2023-08-18 21:03:41 +00:00
Sarah Jamie Lewis cd87779e87 Merge branch 'master' into whonix
continuous-integration/drone/pr Build is pending Details
2023-08-18 21:03:33 +00:00
Sarah Jamie Lewis d8dd82d065 Update Docs
continuous-integration/drone/pr Build is pending Details
2023-08-16 10:59:31 -07:00
Sarah Jamie Lewis 932f99fac8 Expand Useable Ports...these apply to hosted servers too..
continuous-integration/drone/pr Build is pending Details
2023-08-16 10:56:43 -07:00
Sarah Jamie Lewis bbacb5539d Documentation
continuous-integration/drone/pr Build is pending Details
2023-08-16 10:49:25 -07:00
Sarah Jamie Lewis 2c9ec9d894 Clean up and seperate flags 2023-08-16 10:46:02 -07:00
Sarah Jamie Lewis c9ea1e4464 Comment os.ID 2023-08-16 10:33:12 -07:00
Sarah Jamie Lewis 61ced82cb4 Restrict Ports when BINE_WHONIX is enabled. 2023-08-16 10:31:48 -07:00
Sarah Jamie Lewis 91c41e2005 Merge pull request 'Support Whonix' (#46) from whonix into master
continuous-integration/drone/push Build is pending Details
Reviewed-on: #46
Reviewed-by: Dan Ballard <dan@openprivacy.ca>
2023-08-15 17:21:08 +00:00
Sarah Jamie Lewis caca121441 Support Whonix
continuous-integration/drone/pr Build is passing Details
2023-08-14 13:59:58 -07:00
Sarah Jamie Lewis 9beff8a10a Require error to construct an ErrorACN
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2023-05-29 10:22:36 -07:00
Sarah Jamie Lewis dedcbdd3cb Merge pull request 'Fix errorAcn reference issues + add support for Tor specific shared library path' (#43) from tor-updates into master
continuous-integration/drone/push Build is pending Details
Reviewed-on: #43
Reviewed-by: Dan Ballard <dan@openprivacy.ca>
2023-05-24 19:24:20 +00:00
Sarah Jamie Lewis c18cd719a1 update readme
continuous-integration/drone/pr Build is pending Details
2023-05-24 12:19:15 -07:00
Sarah Jamie Lewis 380fd1834a Fix errorAcn reference issues + add support for Tor specific shared library path
continuous-integration/drone/pr Build is passing Details
2023-05-24 11:11:18 -07:00
Sarah Jamie Lewis 1162cf4168 Upgrade Bine
continuous-integration/drone/push Build is pending Details
2023-04-05 02:38:26 +00:00
Sarah Jamie Lewis 4a2eeed072 Merge pull request 'add TakeOwnership call once tor started to help ensure tor ends; add support for __OwningControllerProcess' (#39) from takeownpid into master
continuous-integration/drone/push Build is pending Details
Reviewed-on: #39
2022-10-08 22:45:46 +00:00
Dan Ballard 2f3860eb89 drone use go 1.19.1
continuous-integration/drone/pr Build is passing Details
2022-10-08 15:26:39 -07:00
Dan Ballard bbe4198a41 add TakeOwnership call once tor started to help ensure tor ends; add support for __OwningControllerProcess 2022-10-08 11:58:42 -07:00
Sarah Jamie Lewis 51029af959 Merge pull request 'enable logging support for torrc builder' (#36) from logging into master
continuous-integration/drone/push Build is pending Details
Reviewed-on: #36
Reviewed-by: Sarah Jamie Lewis <sarah@openprivacy.ca>
2022-09-21 21:39:19 +00:00
Dan Ballard 1f52dc7138 enable logging support for torrc builder
continuous-integration/drone/pr Build is passing Details
2022-09-21 13:06:15 -07:00
Sarah Jamie Lewis 72f689de26 Merge pull request 'update .drone.yml to new format' (#35) from updateDrone into master
continuous-integration/drone/push Build is passing Details
Reviewed-on: #35
2022-09-07 15:39:47 +00:00
Dan Ballard 69085fc721 update .drone.yml to new format
continuous-integration/drone/pr Build is passing Details
2022-09-06 19:14:42 -07:00
Sarah Jamie Lewis 98b15bd105 Merge pull request 'store bootsrap version, make available; fix tor version parsing for double digit versions' (#33) from verStatus into master
continuous-integration/drone/push Build was killed Details
continuous-integration/drone/tag Build was killed Details
Reviewed-on: #33
2022-08-29 02:56:41 +00:00
Dan Ballard 789de52589 store bootsrap version, make available; fix tor version parsing for double digit versions
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2022-08-28 19:50:01 -07:00
Dan Ballard 1741e63424 Merge pull request 'Upgrade Dependencies. Prevent socks default behaviour in bine' (#34) from binefix into master
continuous-integration/drone/push Build is passing Details
Reviewed-on: #34
2022-08-29 02:05:30 +00:00
Sarah Jamie Lewis 478df967fc Upgrade Dependencies. Prevent socks default behaviour in bine
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-08-28 18:57:45 -07:00
Sarah Jamie Lewis 8e5dc44400 Merge pull request 'Get x Callbacks' (#32) from gets into master
continuous-integration/drone/push Build was killed Details
continuous-integration/drone/tag Build was killed Details
Reviewed-on: #32
2022-08-08 19:33:00 +00:00
Dan Ballard 6122ad437d Get x Callbacks
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build was killed Details
2022-08-08 12:24:16 -07:00
Dan Ballard 882849b66a Merge pull request 'add versionCallback and use on reboot tor; change start preference for bundled tor; new go 1.17 build directives' (#31) from versionCallback into master
continuous-integration/drone/push Build is failing Details
continuous-integration/drone/tag Build is passing Details
Reviewed-on: #31
2022-08-04 05:15:03 +00:00
Dan Ballard 9a134810c3 remove unused function
continuous-integration/drone/push Build is pending Details
continuous-integration/drone/pr Build is passing Details
2022-08-03 19:21:03 -07:00
Dan Ballard 9992edadba add testing to versionCallback, fix versionCallback
continuous-integration/drone/push Build is pending Details
continuous-integration/drone/pr Build is failing Details
2022-08-02 19:45:44 -07:00
Dan Ballard 9d4e1e5ca5 add versionCallback and use on reboot tor; change start preference for bundled tor; new go 1.17 build directives 2022-08-01 10:50:55 -07:00
Dan Ballard b280f7a1fe Merge pull request 'Make regexp global static' (#30) from regexp_static into master
continuous-integration/drone/tag Build is passing Details
continuous-integration/drone/push Build is passing Details
Reviewed-on: #30
2022-04-19 23:54:25 +00:00
Sarah Jamie Lewis cc924a476e Make regexp global static
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2022-04-19 16:42:23 -07:00
Sarah Jamie Lewis 8d0ed17e7f Fix several small goroutine leaks around restart
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-04-18 14:59:37 -07:00
15 changed files with 392 additions and 152 deletions

View File

@ -1,63 +1,76 @@
workspace: ---
base: /go kind: pipeline
path: src/git.openprivacy.ca/openprivacy/connectivity type: docker
name: linux-test
pipeline: steps:
fetch: - name: fetch
when: image: golang:1.19.1
repo: openprivacy/connectivity volumes:
branch: master - name: deps
event: [ push, pull_request ] path: /go
image: golang
commands: commands:
- go install honnef.co/go/tools/cmd/staticcheck@latest - go install honnef.co/go/tools/cmd/staticcheck@latest
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/tor -P tmp/ - wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/tor -P tmp/
- chmod a+x tmp/tor - chmod a+x tmp/tor
- go mod download - go mod download
quality: - name: quality
when: image: golang:1.19.1
repo: openprivacy/connectivity volumes:
branch: master - name: deps
event: [ push, pull_request ] path: /go
image: golang
commands: commands:
- staticcheck ./... - staticcheck ./...
units-tests: - name: units-tests
when: image: golang:1.19.1
repo: openprivacy/connectivity volumes:
branch: master - name: deps
event: [ push, pull_request ] path: /go
image: golang
commands: commands:
- export PATH=$PATH:/go/src/git.openprivacy.ca/openprivacy/connectivity - export PATH=`pwd`:$PATH
- ./tmp/tor -f ./testing/torrc - ./tmp/tor -f ./testing/torrc
- sleep 15 - sleep 15
- sh testing/tests.sh - sh testing/tests.sh
- pkill -9 tor - pkill -9 tor
integration-tests: - name: integration-tests
when: image: golang:1.19.1
repo: openprivacy/connectivity volumes:
branch: master - name: deps
event: [ push, pull_request ] path: /go
image: golang
commands: commands:
- export PATH=`pwd`:$PATH
- go test -race -v ./testing/launch_tor_integration_test.go - go test -race -v ./testing/launch_tor_integration_test.go
notify-email: - name: notify-email
image: drillster/drone-email image: drillster/drone-email
pull: if-not-exists
host: build.openprivacy.ca host: build.openprivacy.ca
port: 25 port: 25
skip_verify: true skip_verify: true
from: drone@openprivacy.ca from: drone@openprivacy.ca
when: when:
repo: openprivacy/connectivity
branch: master
status: [ failure ] status: [ failure ]
notify-gogs: - name: notify-gogs
image: openpriv/drone-gogs image: openpriv/drone-gogs
pull: if-not-exists
when: when:
repo: openprivacy/connectivity
branch: master
event: pull_request event: pull_request
status: [ success, changed, failure ] status: [ success, changed, failure ]
secrets: [gogs_account_token] environment:
gogs_url: https://git.openprivacy.ca GOGS_ACCOUNT_TOKEN:
from_secret: gogs_account_token
settings:
gogs_url: https://git.openprivacy.ca
volumes:
# gopath where bin and pkg lives to persist across steps
- name: deps
temp: {}
trigger:
repo: openprivacy/connectivity
branch: master
event:
- push
- pull_request
- tag

4
.gitignore vendored
View File

@ -4,4 +4,6 @@ tor/tor/
vendor/ vendor/
*.cover.out *.cover.out
tmp/ tmp/
testing/tor/* testing/tor/*
tor/data-dir*
testing/data-dir*

View File

@ -7,6 +7,12 @@ A library providing an ACN (Anonymous Communication Network
* Tor v3 Onion Services * Tor v3 Onion Services
## Environment Variables
- `TOR_LD_LIBRARY_PATH` - override the library path given to the Tor process as different from the one given to the parent process.
- `CWTCH_RESTRICT_PORTS` - forces connectivity to bind to a subset of ports `15000-15378`
- `CWTCH_BIND_EXTERNAL_WHONIX` - forces connectivity to bind to external interfaces (only supported/recommended on certain Whonix-based setups. Please open an issue if you think this should be expanded.)
## Requirements for ACN Support ## Requirements for ACN Support
* Reference an EndPoint via a string / hostname * Reference an EndPoint via a string / hostname
@ -50,4 +56,4 @@ service:
acn.Restart() acn.Restart()
and and
acn.Close() acn.Close()

9
acn.go
View File

@ -39,6 +39,13 @@ type ACN interface {
// Sets the callback function to be called when ACN status changes // Sets the callback function to be called when ACN status changes
SetStatusCallback(callback func(int, string)) SetStatusCallback(callback func(int, string))
GetStatusCallback() func(int, string)
// Sets the callback function to be called when ACN reboots to emit the version
SetVersionCallback(callback func(string))
GetVersionCallback() func(string)
// Restarts the underlying connection // Restarts the underlying connection
Restart() Restart()
@ -55,8 +62,6 @@ type ACN interface {
// GetVersion returns a string of what the ACN returns when asked for a version // GetVersion returns a string of what the ACN returns when asked for a version
GetVersion() string GetVersion() string
Callback() func(int, string)
GetInfo(onion string) (map[string]string, error) GetInfo(onion string) (map[string]string, error)
Close() Close()

View File

@ -1,57 +1,72 @@
package connectivity package connectivity
import ( import (
"errors"
"fmt"
"net" "net"
) )
const acnError = "error initializing anonymous communication network"
// ErrorACN - a status-callback safe errored ACN. Use this when ACN construction goes wrong // 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. // and you need a safe substitute that can later be replaced with a working ACN without impacting calling clients.
type ErrorACN struct { type ErrorACN struct {
statusCallbackCache func(int, string) acnError error
statusCallbackCache func(int, string)
versionCallbackCache func(string)
} }
func (e ErrorACN) Callback() func(int, string) { func NewErrorACN(err error) ErrorACN {
return ErrorACN{
acnError: err,
statusCallbackCache: func(int, string) {},
versionCallbackCache: func(string) {},
}
}
func (e *ErrorACN) GetStatusCallback() func(int, string) {
return e.statusCallbackCache return e.statusCallbackCache
} }
func (e *ErrorACN) GetVersionCallback() func(string) {
return e.versionCallbackCache
}
func (e *ErrorACN) GetInfo(addr string) (map[string]string, error) { func (e *ErrorACN) GetInfo(addr string) (map[string]string, error) {
return nil, errors.New(acnError) return nil, e.acnError
} }
func (e ErrorACN) GetBootstrapStatus() (int, string) { func (e *ErrorACN) GetBootstrapStatus() (int, string) {
return -1, acnError return -1, e.acnError.Error()
} }
func (e ErrorACN) WaitTillBootstrapped() error { func (e *ErrorACN) WaitTillBootstrapped() error {
return errors.New(acnError) return e.acnError
} }
func (e *ErrorACN) SetStatusCallback(callback func(int, string)) { func (e *ErrorACN) SetStatusCallback(callback func(int, string)) {
e.statusCallbackCache = callback e.statusCallbackCache = callback
} }
func (e ErrorACN) Restart() { func (e *ErrorACN) SetVersionCallback(callback func(string)) {
e.versionCallbackCache = callback
} }
func (e ErrorACN) Open(hostname string) (net.Conn, string, error) { func (e *ErrorACN) Restart() {
return nil, "", fmt.Errorf(acnError)
} }
func (e ErrorACN) Listen(identity PrivateKey, port int) (ListenService, error) { func (e *ErrorACN) Open(hostname string) (net.Conn, string, error) {
return nil, fmt.Errorf(acnError) return nil, "", e.acnError
} }
func (e ErrorACN) GetPID() (int, error) { func (e *ErrorACN) Listen(identity PrivateKey, port int) (ListenService, error) {
return -1, fmt.Errorf(acnError) return nil, e.acnError
} }
func (e ErrorACN) GetVersion() string { func (e *ErrorACN) GetPID() (int, error) {
return acnError return -1, e.acnError
} }
func (e ErrorACN) Close() { func (e *ErrorACN) GetVersion() string {
return e.acnError.Error()
}
func (e *ErrorACN) Close() {
// nothing to do...
} }

16
go.mod
View File

@ -1,11 +1,15 @@
module git.openprivacy.ca/openprivacy/connectivity module git.openprivacy.ca/openprivacy/connectivity
go 1.13 go 1.17
require ( require (
filippo.io/edwards25519 v1.0.0-rc.1 filippo.io/edwards25519 v1.0.0
git.openprivacy.ca/openprivacy/bine v0.0.4 git.openprivacy.ca/openprivacy/bine v0.0.5
git.openprivacy.ca/openprivacy/log v1.0.2 git.openprivacy.ca/openprivacy/log v1.0.3
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect )
require (
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
) )

26
go.sum
View File

@ -1,9 +1,9 @@
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
git.openprivacy.ca/openprivacy/bine v0.0.4 h1:CO7EkGyz+jegZ4ap8g5NWRuDHA/56KKvGySR6OBPW+c= git.openprivacy.ca/openprivacy/bine v0.0.5 h1:DJs5gqw3SkvLSgRDvroqJxZ7F+YsbxbBRg5t0rU5gYE=
git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU= git.openprivacy.ca/openprivacy/bine v0.0.5/go.mod h1:fwdeq6RO08WDkV0k7HfArsjRvurVULoUQmT//iaABZM=
git.openprivacy.ca/openprivacy/log v1.0.2 h1:HLP4wsw4ljczFAelYnbObIs821z+jgMPCe8uODPnGQM= git.openprivacy.ca/openprivacy/log v1.0.3 h1:E/PMm4LY+Q9s3aDpfySfEDq/vYQontlvNj/scrPaga0=
git.openprivacy.ca/openprivacy/log v1.0.2/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= git.openprivacy.ca/openprivacy/log v1.0.3/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -13,23 +13,25 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -18,10 +18,14 @@ func NewLocalACN() ACN {
return &localProvider{} return &localProvider{}
} }
func (lp *localProvider) Callback() func(int, string) { func (lp *localProvider) GetStatusCallback() func(int, string) {
return func(int, string) {} return func(int, string) {}
} }
func (lp *localProvider) GetVersionCallback() func(string) {
return func(string) {}
}
func (ls *localListenService) AddressFull() string { func (ls *localListenService) AddressFull() string {
return ls.l.Addr().String() return ls.l.Addr().String()
} }
@ -51,6 +55,10 @@ func (lp *localProvider) SetStatusCallback(callback func(int, string)) {
// nop // nop
} }
func (lp *localProvider) SetVersionCallback(callback func(string)) {
// nop
}
func (lp *localProvider) GetPID() (int, error) { func (lp *localProvider) GetPID() (int, error) {
return 0, nil return 0, nil
} }

View File

@ -29,7 +29,8 @@ func (p *ProxyACN) ReplaceACN(acn ACN) {
p.lock.Lock() p.lock.Lock()
defer p.lock.Unlock() defer p.lock.Unlock()
p.acn.Close() p.acn.Close()
acn.SetStatusCallback(p.acn.Callback()) acn.SetStatusCallback(p.acn.GetStatusCallback())
acn.SetVersionCallback(p.acn.GetVersionCallback())
p.acn = acn p.acn = acn
} }
@ -49,6 +50,10 @@ func (p *ProxyACN) SetStatusCallback(callback func(int, string)) {
p.acn.SetStatusCallback(callback) p.acn.SetStatusCallback(callback)
} }
func (p *ProxyACN) SetVersionCallback(callback func(string)) {
p.acn.SetVersionCallback(callback)
}
func (p *ProxyACN) Restart() { func (p *ProxyACN) Restart() {
p.acn.Restart() p.acn.Restart()
} }
@ -73,6 +78,10 @@ func (p *ProxyACN) Close() {
p.acn.Close() p.acn.Close()
} }
func (p *ProxyACN) Callback() func(int, string) { func (p *ProxyACN) GetStatusCallback() func(int, string) {
return p.acn.Callback() return p.acn.GetStatusCallback()
}
func (p *ProxyACN) GetVersionCallback() func(string) {
return p.acn.GetVersionCallback()
} }

View File

@ -3,15 +3,17 @@ package testing
import ( import (
"git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/connectivity/tor"
"git.openprivacy.ca/openprivacy/log" "git.openprivacy.ca/openprivacy/log"
"io/ioutil"
"math/rand" "math/rand"
"os" "os"
path "path/filepath" path "path/filepath"
"runtime"
"runtime/pprof"
"testing" "testing"
"time" "time"
) )
func TestLaunchTor(t *testing.T) { func TestLaunchTor(t *testing.T) {
goRoutineStart := runtime.NumGoroutine()
log.SetLevel(log.LevelDebug) log.SetLevel(log.LevelDebug)
rand.Seed(int64(time.Now().Nanosecond())) rand.Seed(int64(time.Now().Nanosecond()))
@ -27,7 +29,7 @@ func TestLaunchTor(t *testing.T) {
} }
dataDir := "" dataDir := ""
if dataDir, err = ioutil.TempDir(path.Join("..", "testing"), "data-dir-"); err != nil { if dataDir, err = os.MkdirTemp(path.Join("..", "testing"), "data-dir-"); err != nil {
t.Fatalf("could not create data dir") t.Fatalf("could not create data dir")
} }
@ -51,4 +53,15 @@ func TestLaunchTor(t *testing.T) {
t.Log("we have bootstrapped!") t.Log("we have bootstrapped!")
acn.Close() acn.Close()
} }
time.Sleep(time.Second * 5)
goRoutineEnd := runtime.NumGoroutine()
if goRoutineEnd != goRoutineStart {
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
t.Fatalf("goroutine leak in ACN: %v %v", goRoutineStart, goRoutineEnd)
}
} }

View File

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows // +build !windows
package tor package tor

View File

@ -1,3 +1,4 @@
//go:build windows
// +build windows // +build windows
package tor package tor

View File

@ -1,6 +1,7 @@
package tor package tor
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"fmt" "fmt"
@ -69,10 +70,13 @@ type torProvider struct {
breakChan chan bool breakChan chan bool
childListeners map[string]*onionListenService childListeners map[string]*onionListenService
statusCallback func(int, string) statusCallback func(int, string)
versionCallback func(string)
lastRestartTime time.Time lastRestartTime time.Time
authenticator tor.Authenticator authenticator tor.Authenticator
isClosed bool isClosed bool
dataDir string dataDir string
version string
bootProgress int
} }
func (ols *onionListenService) AddressFull() string { func (ols *onionListenService) AddressFull() string {
@ -162,9 +166,13 @@ func (tp *torProvider) GetInfo(onion string) (map[string]string, error) {
return nil, err return nil, err
} }
var progRe = regexp.MustCompile("PROGRESS=([0-9]*)")
var sumRe = regexp.MustCompile("SUMMARY=\"(.*)\"$")
// GetBootstrapStatus returns an int 0-100 on the percent the bootstrapping of the underlying network is at and an optional string message // GetBootstrapStatus returns an int 0-100 on the percent the bootstrapping of the underlying network is at and an optional string message
// returns -1 on network disconnected //
// returns -2 on error // returns -1 on network disconnected
// returns -2 on error
func (tp *torProvider) GetBootstrapStatus() (int, string) { func (tp *torProvider) GetBootstrapStatus() (int, string) {
tp.lock.Lock() tp.lock.Lock()
defer tp.lock.Unlock() defer tp.lock.Unlock()
@ -190,8 +198,6 @@ func (tp *torProvider) GetBootstrapStatus() (int, string) {
status := "" status := ""
if len(kvs) > 0 { if len(kvs) > 0 {
progRe := regexp.MustCompile("PROGRESS=([0-9]*)")
sumRe := regexp.MustCompile("SUMMARY=\"(.*)\"$")
if progMatches := progRe.FindStringSubmatch(kvs[0].Val); len(progMatches) > 1 { if progMatches := progRe.FindStringSubmatch(kvs[0].Val); len(progMatches) > 1 {
progress, _ = strconv.Atoi(progMatches[1]) progress, _ = strconv.Atoi(progMatches[1])
@ -201,23 +207,14 @@ func (tp *torProvider) GetBootstrapStatus() (int, string) {
status = statusMatches[1] status = statusMatches[1]
} }
} }
tp.bootProgress = progress
return progress, status return progress, status
} }
func (tp *torProvider) GetVersion() string { func (tp *torProvider) GetVersion() string {
tp.lock.Lock() tp.lock.Lock()
defer tp.lock.Unlock() defer tp.lock.Unlock()
return tp.version
if tp.t == nil {
return "No Tor"
}
pinfo, err := tp.t.Control.ProtocolInfo()
if err == nil {
return pinfo.TorVersion
}
return "No Tor"
} }
func (tp *torProvider) closed() bool { func (tp *torProvider) closed() bool {
@ -271,7 +268,28 @@ func (tp *torProvider) Listen(identity connectivity.PrivateKey, port int) (conne
localport += 1024 localport += 1024
} }
localListener, err := net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(localport)) var localListener net.Listener
var err error
if cwtchRestrictPorts := os.Getenv("CWTCH_RESTRICT_PORTS"); strings.ToLower(cwtchRestrictPorts) == "true" {
// for whonix like systems we tightly restrict possible listen...
// pick a random port between 15000 and 15378
// cwtch = 63 *77 *74* 63* 68 = 1537844616
log.Infof("using restricted ports, CWTCH_RESTRICT_PORTS=true");
localport = 15000 + (localport % 378)
}
if bindExternal := os.Getenv("CWTCH_BIND_EXTERNAL_WHONIX"); strings.ToLower(bindExternal) == "true" {
if _, ferr := os.Stat("/usr/share/anon-ws-base-files/workstation"); !os.IsNotExist(ferr) {
log.Infof("WARNING: binding to external interfaces. This is potentially unsafe outside of a containerized environment.");
localListener, err = net.Listen("tcp", "0.0.0.0:"+strconv.Itoa(localport))
} else {
log.Errorf("CWTCH_BIND_EXTERNAL_WHONIX flag set, but /usr/share/anon-ws-base-files/workstation does not exist. Defaulting to binding to local ports");
localListener, err = net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(localport))
}
} else {
localListener, err = net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(localport))
}
if err != nil { if err != nil {
return nil, err return nil, err
@ -293,6 +311,8 @@ func (tp *torProvider) Listen(identity connectivity.PrivateKey, port int) (conne
return nil, err return nil, err
} }
// We need to set os.ID here, otherwise os.Close() may not shut down the onion service properly...
os.ID = onion
os.CloseLocalListenerOnClose = true os.CloseLocalListenerOnClose = true
ols := &onionListenService{os: os, tp: tp} ols := &onionListenService{os: os, tp: tp}
@ -304,12 +324,12 @@ func (tp *torProvider) Restart() {
log.Debugf("launching restart...") log.Debugf("launching restart...")
tp.lock.Lock() tp.lock.Lock()
defer tp.lock.Unlock()
log.Debugf("checking last restart time") log.Debugf("checking last restart time")
if time.Since(tp.lastRestartTime) < restartCooldown { if time.Since(tp.lastRestartTime) < restartCooldown {
tp.lock.Unlock()
return return
} }
tp.lock.Unlock()
go tp.restart() go tp.restart()
} }
@ -322,6 +342,7 @@ func (tp *torProvider) restart() {
// preserve status callback after shutdown // preserve status callback after shutdown
statusCallback := tp.statusCallback statusCallback := tp.statusCallback
versionCallback := tp.versionCallback
tp.t = nil tp.t = nil
log.Debugf("Restarting Tor Process") log.Debugf("Restarting Tor Process")
@ -332,7 +353,12 @@ func (tp *torProvider) restart() {
tp.t = newTp.t tp.t = newTp.t
tp.dialer = newTp.dialer tp.dialer = newTp.dialer
tp.statusCallback = statusCallback tp.statusCallback = statusCallback
tp.versionCallback = versionCallback
if tp.versionCallback != nil {
tp.versionCallback(tp.version)
}
tp.lastRestartTime = time.Now() tp.lastRestartTime = time.Now()
tp.isClosed = false
go tp.monitorRestart() go tp.monitorRestart()
} else { } else {
log.Errorf("Error restarting Tor process: %v", err) log.Errorf("Error restarting Tor process: %v", err)
@ -346,6 +372,10 @@ func (tp *torProvider) Open(hostname string) (net.Conn, string, error) {
tp.lock.Unlock() tp.lock.Unlock()
return nil, hostname, errors.New("tor is offline") return nil, hostname, errors.New("tor is offline")
} }
if tp.bootProgress != 100 {
tp.lock.Unlock()
return nil, hostname, fmt.Errorf("tor not online, bootstrap progress only %v", tp.bootProgress)
}
tp.lock.Unlock() tp.lock.Unlock()
resolvedHostname := hostname resolvedHostname := hostname
@ -353,14 +383,24 @@ func (tp *torProvider) Open(hostname string) (net.Conn, string, error) {
addrParts := strings.Split(hostname, ":") addrParts := strings.Split(hostname, ":")
resolvedHostname = addrParts[1] resolvedHostname = addrParts[1]
} }
conn, err := tp.dialer.Dial("tcp", resolvedHostname+".onion:9878") conn, err := tp.dialer.Dial("tcp", resolvedHostname+".onion:9878")
return conn, resolvedHostname, err return conn, resolvedHostname, err
} }
func (tp *torProvider) Close() { func (tp *torProvider) Close() {
closing := false
tp.lock.Lock() tp.lock.Lock()
defer tp.lock.Unlock() defer tp.lock.Unlock()
if !tp.isClosed {
// Break out of any background checks and close
// the underlying tor connection
tp.isClosed = true
tp.breakChan <- true
// wiggle lock to make sure if monitorRestart is waiting for the lock, it gets it, and finished that branch and gets the channel request to exit
tp.lock.Unlock()
tp.lock.Lock()
closing = true
}
// Unregister Child Listeners // Unregister Child Listeners
for addr, child := range tp.childListeners { for addr, child := range tp.childListeners {
@ -368,11 +408,9 @@ func (tp *torProvider) Close() {
delete(tp.childListeners, addr) delete(tp.childListeners, addr)
} }
if !tp.isClosed { log.Debugf("shutting down acn threads..(is already closed: %v)", tp.isClosed)
// Break out of any background checks and close
// the underlying tor connection if closing {
tp.isClosed = true
tp.breakChan <- true
if tp.t != nil { if tp.t != nil {
tp.t.Close() tp.t.Close()
tp.t = nil tp.t = nil
@ -386,24 +424,37 @@ func (tp *torProvider) SetStatusCallback(callback func(int, string)) {
tp.statusCallback = callback tp.statusCallback = callback
} }
func (tp *torProvider) Callback() func(int, string) { func (tp *torProvider) SetVersionCallback(callback func(string)) {
tp.lock.Lock()
defer tp.lock.Unlock()
tp.versionCallback = callback
}
func (tp *torProvider) GetStatusCallback() func(int, string) {
tp.lock.Lock() tp.lock.Lock()
defer tp.lock.Unlock() defer tp.lock.Unlock()
return tp.statusCallback return tp.statusCallback
} }
func (tp *torProvider) GetVersionCallback() func(string) {
tp.lock.Lock()
defer tp.lock.Unlock()
return tp.versionCallback
}
func (tp *torProvider) callStatusCallback(prog int, status string) { func (tp *torProvider) callStatusCallback(prog int, status string) {
tp.lock.Lock() tp.lock.Lock()
defer tp.lock.Unlock()
if tp.statusCallback != nil { if tp.statusCallback != nil {
tp.statusCallback(prog, status) tp.statusCallback(prog, status)
} }
tp.lock.Unlock()
} }
// NewTorACNWithAuth creates/starts a Tor ACN and returns a usable ACN object // NewTorACNWithAuth creates/starts a Tor ACN and returns a usable ACN object
func NewTorACNWithAuth(appDirectory string, bundledTorPath string, dataDir string, controlPort int, authenticator tor.Authenticator) (connectivity.ACN, error) { func NewTorACNWithAuth(appDirectory string, bundledTorPath string, dataDir string, controlPort int, authenticator tor.Authenticator) (connectivity.ACN, error) {
tp, err := startTor(appDirectory, bundledTorPath, dataDir, controlPort, authenticator) tp, err := startTor(appDirectory, bundledTorPath, dataDir, controlPort, authenticator)
if err == nil { if err == nil {
tp.isClosed = false
go tp.monitorRestart() go tp.monitorRestart()
} }
return tp, err return tp, err
@ -419,11 +470,19 @@ func newHideCmd(exePath string) process.Creator {
cmd.Stdout = loggerDebug cmd.Stdout = loggerDebug
cmd.Stderr = loggerError cmd.Stderr = loggerError
cmd.SysProcAttr = sysProcAttr cmd.SysProcAttr = sysProcAttr
// override tor ld_library_path if requested
torLdLibPath, exists := os.LookupEnv("TOR_LD_LIBRARY_PATH")
if exists {
ldLibPath := fmt.Sprintf("LD_LIBRARY_PATH=%v", torLdLibPath)
cmd.Env = append([]string{ldLibPath}, os.Environ()...)
}
return cmd, nil return cmd, nil
}) })
} }
func (tp *torProvider) checkVersion() error { func (tp *torProvider) checkVersion() (string, error) {
// attempt connect to system tor // attempt connect to system tor
log.Debugf("dialing tor control port") log.Debugf("dialing tor control port")
controlport, err := dialControlPort(tp.controlPort) controlport, err := dialControlPort(tp.controlPort)
@ -436,13 +495,13 @@ func (tp *torProvider) checkVersion() error {
if err == nil { if err == nil {
if minTorVersionReqs(pinfo.TorVersion) { if minTorVersionReqs(pinfo.TorVersion) {
log.Debugln("OK version " + pinfo.TorVersion) log.Debugln("OK version " + pinfo.TorVersion)
return nil return pinfo.TorVersion, nil
} }
return fmt.Errorf("tor version not supported: %v", pinfo.TorVersion) return pinfo.TorVersion, fmt.Errorf("tor version not supported: %v", pinfo.TorVersion)
} }
} }
} }
return err return "", err
} }
func startTor(appDirectory string, bundledTorPath string, dataDir string, controlPort int, authenticator tor.Authenticator) (*torProvider, error) { func startTor(appDirectory string, bundledTorPath string, dataDir string, controlPort int, authenticator tor.Authenticator) (*torProvider, error) {
@ -450,17 +509,18 @@ func startTor(appDirectory string, bundledTorPath string, dataDir string, contro
os.MkdirAll(torDir, 0700) os.MkdirAll(torDir, 0700)
tp := &torProvider{authenticator: authenticator, controlPort: controlPort, appDirectory: appDirectory, bundeledTorPath: bundledTorPath, childListeners: make(map[string]*onionListenService), breakChan: make(chan bool), statusCallback: nil, lastRestartTime: time.Now().Add(-restartCooldown)} tp := &torProvider{authenticator: authenticator, controlPort: controlPort, appDirectory: appDirectory, bundeledTorPath: bundledTorPath, childListeners: make(map[string]*onionListenService), breakChan: make(chan bool, 1), statusCallback: nil, versionCallback: nil, lastRestartTime: time.Now().Add(-restartCooldown), bootProgress: -1}
log.Debugf("checking if there is a running system tor") log.Debugf("checking if there is a running system tor")
if err := tp.checkVersion(); err == nil { if version, err := tp.checkVersion(); err == nil {
tp.version = version
controlport, err := dialControlPort(tp.controlPort) controlport, err := dialControlPort(tp.controlPort)
if err == nil { if err == nil {
log.Debugf("creating tor handler from system tor") log.Debugf("creating tor handler from system tor")
tp.t = createFromExisting(controlport, dataDir) tp.t = createFromExisting(controlport, dataDir)
tp.dialer, err = tp.t.Dialer(context.TODO(), &tor.DialConf{Authenticator: tp.authenticator})
return tp, err
} }
tp.dialer, err = tp.t.Dialer(context.TODO(), &tor.DialConf{Authenticator: tp.authenticator})
return tp, err
} }
// check if the torrc file is present where expected // check if the torrc file is present where expected
@ -470,32 +530,36 @@ func startTor(appDirectory string, bundledTorPath string, dataDir string, contro
return nil, err return nil, err
} }
// if not, try running system tor // if not, try bundled tor, then running system tor
log.Debugln("checking if we can run system installed tor or bundled tor") log.Debugln("checking if we can run bundled tor or system installed tor")
if checkCmdlineTorVersion("tor") { if version, pass := checkCmdlineTorVersion(bundledTorPath); pass {
t, err := tor.Start(context.TODO(), &tor.StartConf{ControlPort: tp.controlPort, DisableCookieAuth: true, UseEmbeddedControlConn: false, DisableEagerAuth: true, EnableNetwork: true, DataDir: dataDir, TorrcFile: path.Join(torDir, "torrc"), DebugWriter: nil, ProcessCreator: newHideCmd("tor")})
if err != nil {
log.Debugf("Error connecting to self-run system tor: %v\n", err)
return nil, err
}
tp.t = t
} else if bundledTorPath != "" && checkCmdlineTorVersion(bundledTorPath) {
log.Debugln("bundled tor appears viable, attempting to use '" + bundledTorPath + "'") log.Debugln("bundled tor appears viable, attempting to use '" + bundledTorPath + "'")
t, err := tor.Start(context.TODO(), &tor.StartConf{ControlPort: tp.controlPort, DisableCookieAuth: true, UseEmbeddedControlConn: false, DisableEagerAuth: true, EnableNetwork: true, DataDir: dataDir, TorrcFile: path.Join(torDir, "torrc"), ExePath: bundledTorPath, DebugWriter: nil, ProcessCreator: newHideCmd(bundledTorPath)}) t, err := tor.Start(context.TODO(), &tor.StartConf{ControlPort: tp.controlPort, NoAutoSocksPort: true, DisableCookieAuth: true, UseEmbeddedControlConn: false, DisableEagerAuth: true, EnableNetwork: true, DataDir: dataDir, TorrcFile: path.Join(torDir, "torrc"), ExePath: bundledTorPath, DebugWriter: nil, ProcessCreator: newHideCmd(bundledTorPath)})
if err != nil { if err != nil {
log.Debugf("Error running bundled tor %v\n", err) log.Debugf("Error running bundled tor %v\n", err)
return nil, err return nil, err
} }
tp.t = t tp.t = t
tp.version = version
} else if version, pass := checkCmdlineTorVersion("tor"); pass {
t, err := tor.Start(context.TODO(), &tor.StartConf{ControlPort: tp.controlPort, NoAutoSocksPort: true, DisableCookieAuth: true, UseEmbeddedControlConn: false, DisableEagerAuth: true, EnableNetwork: true, DataDir: dataDir, TorrcFile: path.Join(torDir, "torrc"), DebugWriter: nil, ProcessCreator: newHideCmd("tor")})
if err != nil {
log.Debugf("Error connecting to self-run system tor: %v\n", err)
return nil, err
}
tp.t = t
tp.version = version
} else { } else {
log.Debugln("Could not find a viable tor running or to run") 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() version, err := tp.checkVersion()
if err == nil { if err == nil {
tp.t.DeleteDataDirOnClose = false // caller is repsonsible for dealing with cached information... tp.t.DeleteDataDirOnClose = false // caller is responsible for dealing with cached information...
tp.dialer, err = tp.t.Dialer(context.TODO(), &tor.DialConf{Authenticator: tp.authenticator}) tp.dialer, err = tp.t.Dialer(context.TODO(), &tor.DialConf{Authenticator: tp.authenticator})
tp.version = version
tp.t.Control.TakeOwnership()
return tp, err return tp, err
} }
return nil, fmt.Errorf("could not connect to running tor: %v", err) return nil, fmt.Errorf("could not connect to running tor: %v", err)
@ -523,7 +587,6 @@ func (tp *torProvider) monitorRestart() {
prog, status := tp.GetBootstrapStatus() prog, status := tp.GetBootstrapStatus()
if prog == torDown && tp.t != nil { if prog == torDown && tp.t != nil {
log.Warnf("monitorRestart calling tp.restart() with prog:%v\n", prog)
tp.restart() tp.restart()
return return
} }
@ -566,25 +629,57 @@ func createFromExisting(controlport *control.Conn, datadir string) *tor.Tor {
return t return t
} }
func checkCmdlineTorVersion(torCmd string) bool { func checkCmdlineTorVersion(torCmd string) (string, bool) {
if torCmd == "" {
return "", false
}
// ideally we would use CommandContext with Timeout here
// but it doesn't work with programs that may launch other processes via scripts e.g. exec
// and the workout is more complex than just implementing the logic ourselves...
cmd := exec.Command(torCmd, "--version") cmd := exec.Command(torCmd, "--version")
cmd.SysProcAttr = sysProcAttr var outb bytes.Buffer
out, err := cmd.CombinedOutput() cmd.Stdout = &outb
re := regexp.MustCompile(`[0-1]\.[0-9]\.[0-9]\.[0-9]`)
sysTorVersion := re.Find(out) waiting := make(chan error, 1)
log.Infoln("tor version: " + string(sysTorVersion))
return err == nil && minTorVersionReqs(string(sysTorVersion)) // try running the tor process
go func() {
log.Debugf("running tor process: %v", torCmd)
cmd.Run()
waiting <- nil
}()
// timeout function
go func() {
<-time.After(time.Second * 5)
waiting <- errors.New("timeout")
}()
err := <-waiting
if err != nil {
log.Debugf("tor process timed out")
return "", false
}
re := regexp.MustCompile(`[0-1]+\.[0-9]+\.[0-9]+\.[0-9]+`)
sysTorVersion := re.Find(outb.Bytes())
log.Infof("tor version: %v", string(sysTorVersion))
return string(sysTorVersion), minTorVersionReqs(string(sysTorVersion))
} }
// returns true if supplied version meets our min requirments // returns true if supplied version meets our min requirments
// min requirement: 0.3.5.x // min requirement: 0.3.5.x
func minTorVersionReqs(torversion string) bool { func minTorVersionReqs(torversion string) bool {
torversions := strings.Split(torversion, ".") //eg: 0.3.4.8 or 0.3.5.1-alpha torversions := strings.Split(torversion, ".") //eg: 0.3.4.8 or 0.3.5.1-alpha
log.Debugf("torversions: %v", torversions) if len(torversions) >= 3 {
tva, _ := strconv.Atoi(torversions[0]) log.Debugf("torversions: %v", torversions)
tvb, _ := strconv.Atoi(torversions[1]) tva, _ := strconv.Atoi(torversions[0])
tvc, _ := strconv.Atoi(torversions[2]) tvb, _ := strconv.Atoi(torversions[1])
return tva > 0 || (tva == 0 && (tvb > 3 || (tvb == 3 && tvc >= 5))) tvc, _ := strconv.Atoi(torversions[2])
return tva > 0 || (tva == 0 && (tvb > 3 || (tvb == 3 && tvc >= 5)))
}
return false
} }
func dialControlPort(port int) (*control.Conn, error) { func dialControlPort(port int) (*control.Conn, error) {

View File

@ -3,9 +3,12 @@ package tor
import ( import (
"fmt" "fmt"
"git.openprivacy.ca/openprivacy/log" "git.openprivacy.ca/openprivacy/log"
"io/ioutil" "os"
path "path/filepath" path "path/filepath"
"runtime"
"runtime/pprof"
"testing" "testing"
"time"
) )
func getStatusCallback(progChan chan int) func(int, string) { func getStatusCallback(progChan chan int) func(int, string) {
@ -15,8 +18,19 @@ func getStatusCallback(progChan chan int) func(int, string) {
} }
} }
func getVersionCallback(verChan chan string) func(string) {
return func(version string) {
fmt.Printf("version: %v\n", version)
verChan <- version
}
}
func TestTorProvider(t *testing.T) { func TestTorProvider(t *testing.T) {
progChan := make(chan int)
goRoutineStart := runtime.NumGoroutine()
progChan := make(chan int, 10)
verChan := make(chan string, 10)
log.SetLevel(log.LevelDebug) log.SetLevel(log.LevelDebug)
torpath := path.Join("..", "tmp/tor") torpath := path.Join("..", "tmp/tor")
@ -26,7 +40,7 @@ func TestTorProvider(t *testing.T) {
dataDir := "" dataDir := ""
var err error var err error
if dataDir, err = ioutil.TempDir(path.Join("..", "testing"), "data-dir-"); err != nil { if dataDir, err = os.MkdirTemp(path.Join("..", "testing"), "data-dir-"); err != nil {
t.Fatalf("could not create data dir") t.Fatalf("could not create data dir")
} }
@ -36,6 +50,7 @@ func TestTorProvider(t *testing.T) {
return return
} }
acn.SetStatusCallback(getStatusCallback(progChan)) acn.SetStatusCallback(getStatusCallback(progChan))
acn.SetVersionCallback(getVersionCallback(verChan))
progress := 0 progress := 0
for progress < 100 { for progress < 100 {
@ -50,6 +65,13 @@ func TestTorProvider(t *testing.T) {
progress = <-progChan progress = <-progChan
t.Logf("progress: %v", progress) t.Logf("progress: %v", progress)
} }
log.Debugf("Pulling tor version from version callback chan...\n")
version := <-verChan
if version == "" {
t.Errorf("failed to get tor version, got empty string\n")
} else {
log.Debugf("Tor version: %v\n", version)
}
// Test opening the OP Server // Test opening the OP Server
_, _, err = acn.Open("isbr2t6bflul2zyi6hjtnuezb2xvfr42svzjg2q3gyqfgg3wmnrbkkqd") _, _, err = acn.Open("isbr2t6bflul2zyi6hjtnuezb2xvfr42svzjg2q3gyqfgg3wmnrbkkqd")
@ -79,4 +101,15 @@ func TestTorProvider(t *testing.T) {
acn.Restart() acn.Restart()
acn.Close() acn.Close()
time.Sleep(time.Second * 10)
goRoutineEnd := runtime.NumGoroutine()
if goRoutineEnd != goRoutineStart {
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
t.Fatalf("goroutine leak in ACN: %v %v", goRoutineStart, goRoutineEnd)
}
} }

View File

@ -6,10 +6,18 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "io"
"io/ioutil" "os"
"strings" "strings"
) )
type TorLogLevel string
const TorLogLevelDebug TorLogLevel = "debug"
const TorLogLevelNotice TorLogLevel = "notice"
const TorLogLevelInfo TorLogLevel = "info"
const TorLogLevelWarn TorLogLevel = "warn"
const TorLogLevelErr TorLogLevel = "err"
// TorrcBuilder is a a helper for building torrc files // TorrcBuilder is a a helper for building torrc files
type TorrcBuilder struct { type TorrcBuilder struct {
lines []string lines []string
@ -34,9 +42,28 @@ func (tb *TorrcBuilder) WithControlPort(port int) *TorrcBuilder {
return tb return tb
} }
// WithCustom clobbers the torrc builder and allows the client to set any option they want, while benefiting // WithLog sets the Log to file directive to the specified file with the specified log level
func (tb *TorrcBuilder) WithLog(logfile string, level TorLogLevel) *TorrcBuilder {
tb.lines = append(tb.lines, fmt.Sprintf("Log %v file %v", level, logfile))
return tb
}
// WithSocksTimeout adjusts how long before a timeout error is generated trying to connect to the SOCKS port
func (tb *TorrcBuilder) WithSocksTimeout(timeOutSecs int) *TorrcBuilder {
tb.lines = append(tb.lines, fmt.Sprintf("SocksTimeout %v", timeOutSecs))
return tb
}
// WithCustom appends to the torrc builder and allows the client to set any option they want, while benefiting
// from other configuration options. // from other configuration options.
func (tb *TorrcBuilder) WithCustom(lines []string) *TorrcBuilder { func (tb *TorrcBuilder) WithCustom(lines []string) *TorrcBuilder {
tb.lines = append(tb.lines, lines...)
return tb
}
// UseCustom clobbers the torrc builder and allows the client to set any option they want, while benefiting
// from other configuration options.
func (tb *TorrcBuilder) UseCustom(lines []string) *TorrcBuilder {
tb.lines = lines tb.lines = lines
return tb return tb
} }
@ -52,6 +79,12 @@ func (tb *TorrcBuilder) WithOnionTrafficOnly() *TorrcBuilder {
return tb return tb
} }
// WithOwningPid adds a __OwningControllerProcess line to the config that will attempt to have tor monitor parent PID health and die when parent dies
func (tb *TorrcBuilder) WithOwningPid(pid int) *TorrcBuilder {
tb.lines = append(tb.lines, fmt.Sprintf("__OwningControllerProcess %v", pid))
return tb
}
// WithHashedPassword sets a password for the control port. // WithHashedPassword sets a password for the control port.
func (tb *TorrcBuilder) WithHashedPassword(password string) *TorrcBuilder { func (tb *TorrcBuilder) WithHashedPassword(password string) *TorrcBuilder {
var salt [8]byte var salt [8]byte
@ -65,7 +98,7 @@ func (tb *TorrcBuilder) WithHashedPassword(password string) *TorrcBuilder {
// Build finalizes the torrc contents and write a file // Build finalizes the torrc contents and write a file
func (tb *TorrcBuilder) Build(path string) error { func (tb *TorrcBuilder) Build(path string) error {
return ioutil.WriteFile(path, []byte(strings.Join(tb.lines, "\n")), 0600) return os.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. // Preview provides a string representation of the torrc file without writing it to a file location.