Compare commits

...

57 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
Dan Ballard a30e16354c Merge pull request 'path -> filepath' (#28) from getinfo into master
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
Reviewed-on: #28
2022-01-18 21:01:40 +00:00
Sarah Jamie Lewis 586cc261a3 path -> filepath
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2022-01-18 13:00:41 -08:00
Dan Ballard 3d36e7d4a5 Merge pull request 'Move DataDir Caching Responsibility to Caller to allow Cached Consensus' (#27) from getinfo into master
continuous-integration/drone/push Build is pending Details
continuous-integration/drone/tag Build is pending Details
Reviewed-on: #27
2022-01-18 20:46:56 +00:00
Sarah Jamie Lewis 8ad13a6b36 createFromExisting does not delete data dir on close
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2022-01-18 12:43:47 -08:00
Sarah Jamie Lewis 1a402de50d Fixup Testing Directories
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-01-17 16:03:06 -08:00
Sarah Jamie Lewis 6d5d067d70 Move DataDir Caching Responsibility to Caller to allow Cached Consensus
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is failing Details
2022-01-17 15:55:10 -08:00
erinn 04dec3238b Merge pull request 'Introduce new GetInfo API for fetching information about ACN' (#26) from getinfo into master
continuous-integration/drone/push Build is pending Details
continuous-integration/drone/tag Build is pending Details
Reviewed-on: #26
2022-01-17 20:07:30 +00:00
Sarah Jamie Lewis dfae5b9261 Spelling
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2022-01-17 12:06:36 -08:00
Sarah Jamie Lewis 4353143ae4 Assert CInfo is not empty in Test
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2022-01-13 13:51:58 -08:00
Sarah Jamie Lewis 0c745e7691 Adding Tests for GetInfo 2022-01-13 13:51:58 -08:00
Sarah Jamie Lewis f82cf26731 Allow Querying of ACN Info - Support Getting Circuit Stats 2022-01-13 13:51:58 -08:00
erinn 85da1dac37 Merge pull request 'Allow Custom Tor Config in TorRCBuilder + ProxyACN and ErrorACN' (#25) from custom_tor_config into master
continuous-integration/drone/push Build is pending Details
continuous-integration/drone/tag Build is pending Details
Reviewed-on: #25
2022-01-12 20:16:41 +00:00
Sarah Jamie Lewis 023d1a6e5d Avoid deadlock on double close
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2022-01-12 12:05:07 -08:00
Sarah Jamie Lewis 35247bd044 Clean up ACN Closing Logic
continuous-integration/drone/push Build is pending Details
continuous-integration/drone/pr Build is pending Details
2022-01-12 11:47:17 -08:00
Sarah Jamie Lewis 384d59e9ef WaitTillBootstrapped can now Error
continuous-integration/drone/push Build is pending Details
continuous-integration/drone/pr Build is pending Details
2022-01-11 15:41:56 -08:00
Sarah Jamie Lewis 5d4cf85d26 Add Lock to ReplaceACN. Minor Drone fixups
continuous-integration/drone/push Build is pending Details
continuous-integration/drone/pr Build is pending Details
2022-01-11 15:17:54 -08:00
Sarah Jamie Lewis 13045e3d98 Use staticcheck in drone
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2022-01-11 12:17:26 -08:00
Sarah Jamie Lewis 8a56f02fc5 Merge branch 'custom_tor_config' of git.openprivacy.ca:openprivacy/connectivity into custom_tor_config
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is failing Details
2022-01-11 12:12:21 -08:00
Sarah Jamie Lewis 9169018529 ProxyACN and ErrorACN for nicer ACN Management 2022-01-11 12:11:45 -08:00
Sarah Jamie Lewis 415ca32a32 Merge branch 'master' into custom_tor_config
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2022-01-10 23:38:06 +00:00
Sarah Jamie Lewis dbc3d675ec Update quality to staticcheck
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2022-01-10 12:56:59 -08:00
Sarah Jamie Lewis d3398bd074 Allow Custom Tor Config 2022-01-10 12:53:48 -08:00
18 changed files with 713 additions and 185 deletions

View File

@ -1,64 +1,76 @@
workspace:
base: /go
path: src/git.openprivacy.ca/openprivacy/connectivity
---
kind: pipeline
type: docker
name: linux-test
pipeline:
fetch:
when:
repo: openprivacy/connectivity
branch: master
event: [ push, pull_request ]
image: golang
steps:
- name: fetch
image: golang:1.19.1
volumes:
- name: deps
path: /go
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
branch: master
event: [ push, pull_request ]
image: golang
- name: quality
image: golang:1.19.1
volumes:
- name: deps
path: /go
commands:
- go list ./... | xargs go vet
- go list ./... | xargs golint -set_exit_status
units-tests:
when:
repo: openprivacy/connectivity
branch: master
event: [ push, pull_request ]
image: golang
- staticcheck ./...
- name: units-tests
image: golang:1.19.1
volumes:
- name: deps
path: /go
commands:
- export PATH=$PATH:/go/src/git.openprivacy.ca/openprivacy/connectivity
- export PATH=`pwd`:$PATH
- ./tmp/tor -f ./testing/torrc
- sleep 15
- sh testing/tests.sh
- pkill -9 tor
integration-tests:
when:
repo: openprivacy/connectivity
branch: master
event: [ push, pull_request ]
image: golang
- name: integration-tests
image: golang:1.19.1
volumes:
- name: deps
path: /go
commands:
- export PATH=`pwd`:$PATH
- go test -race -v ./testing/launch_tor_integration_test.go
notify-email:
- name: notify-email
image: drillster/drone-email
pull: if-not-exists
host: build.openprivacy.ca
port: 25
skip_verify: true
from: drone@openprivacy.ca
when:
repo: openprivacy/connectivity
branch: master
status: [ failure ]
notify-gogs:
- name: notify-gogs
image: openpriv/drone-gogs
pull: if-not-exists
when:
repo: openprivacy/connectivity
branch: master
event: pull_request
status: [ success, changed, failure ]
secrets: [gogs_account_token]
gogs_url: https://git.openprivacy.ca
environment:
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

3
.gitignore vendored
View File

@ -4,3 +4,6 @@ tor/tor/
vendor/
*.cover.out
tmp/
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
## 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
* Reference an EndPoint via a string / hostname
@ -50,4 +56,4 @@ service:
acn.Restart()
and
acn.Close()
acn.Close()

14
acn.go
View File

@ -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,10 +35,17 @@ 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))
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
Restart()
@ -58,5 +62,7 @@ type ACN interface {
// GetVersion returns a string of what the ACN returns when asked for a version
GetVersion() string
GetInfo(onion string) (map[string]string, error)
Close()
}

72
error_acn.go Normal file
View File

@ -0,0 +1,72 @@
package connectivity
import (
"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 {
acnError error
statusCallbackCache func(int, string)
versionCallbackCache func(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
}
func (e *ErrorACN) GetVersionCallback() func(string) {
return e.versionCallbackCache
}
func (e *ErrorACN) GetInfo(addr string) (map[string]string, error) {
return nil, e.acnError
}
func (e *ErrorACN) GetBootstrapStatus() (int, string) {
return -1, e.acnError.Error()
}
func (e *ErrorACN) WaitTillBootstrapped() error {
return e.acnError
}
func (e *ErrorACN) SetStatusCallback(callback func(int, string)) {
e.statusCallbackCache = callback
}
func (e *ErrorACN) SetVersionCallback(callback func(string)) {
e.versionCallbackCache = callback
}
func (e *ErrorACN) Restart() {
}
func (e *ErrorACN) Open(hostname string) (net.Conn, string, error) {
return nil, "", e.acnError
}
func (e *ErrorACN) Listen(identity PrivateKey, port int) (ListenService, error) {
return nil, e.acnError
}
func (e *ErrorACN) GetPID() (int, error) {
return -1, e.acnError
}
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
go 1.13
go 1.17
require (
filippo.io/edwards25519 v1.0.0-rc.1
git.openprivacy.ca/openprivacy/bine v0.0.4
git.openprivacy.ca/openprivacy/log v1.0.2
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
filippo.io/edwards25519 v1.0.0
git.openprivacy.ca/openprivacy/bine v0.0.5
git.openprivacy.ca/openprivacy/log v1.0.3
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d
)
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-rc.1/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.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU=
git.openprivacy.ca/openprivacy/log v1.0.2 h1:HLP4wsw4ljczFAelYnbObIs821z+jgMPCe8uODPnGQM=
git.openprivacy.ca/openprivacy/log v1.0.2/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
git.openprivacy.ca/openprivacy/bine v0.0.5 h1:DJs5gqw3SkvLSgRDvroqJxZ7F+YsbxbBRg5t0rU5gYE=
git.openprivacy.ca/openprivacy/bine v0.0.5/go.mod h1:fwdeq6RO08WDkV0k7HfArsjRvurVULoUQmT//iaABZM=
git.openprivacy.ca/openprivacy/log v1.0.3 h1:E/PMm4LY+Q9s3aDpfySfEDq/vYQontlvNj/scrPaga0=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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=
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-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-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-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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
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-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-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-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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/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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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/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=

View File

@ -18,6 +18,14 @@ func NewLocalACN() ACN {
return &localProvider{}
}
func (lp *localProvider) GetStatusCallback() func(int, string) {
return func(int, string) {}
}
func (lp *localProvider) GetVersionCallback() func(string) {
return func(string) {}
}
func (ls *localListenService) AddressFull() string {
return ls.l.Addr().String()
}
@ -34,6 +42,10 @@ func (ls *localListenService) Close() {
ls.l.Close()
}
func (lp *localProvider) GetInfo(addr string) (map[string]string, error) {
return nil, nil
}
// GetBootstrapStatus returns an int 0-100 on the percent the bootstrapping of the underlying network is at and an optional string message
func (lp *localProvider) GetBootstrapStatus() (int, string) {
return 100, "Done"
@ -43,6 +55,10 @@ func (lp *localProvider) SetStatusCallback(callback func(int, string)) {
// nop
}
func (lp *localProvider) SetVersionCallback(callback func(string)) {
// nop
}
func (lp *localProvider) GetPID() (int, error) {
return 0, nil
}
@ -52,7 +68,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) {

87
proxy_acn.go Normal file
View File

@ -0,0 +1,87 @@
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.GetStatusCallback())
acn.SetVersionCallback(p.acn.GetVersionCallback())
p.acn = acn
}
func (p *ProxyACN) GetInfo(addr string) (map[string]string, error) {
return p.acn.GetInfo(addr)
}
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) SetVersionCallback(callback func(string)) {
p.acn.SetVersionCallback(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) GetStatusCallback() func(int, string) {
return p.acn.GetStatusCallback()
}
func (p *ProxyACN) GetVersionCallback() func(string) {
return p.acn.GetVersionCallback()
}

View File

@ -5,12 +5,15 @@ import (
"git.openprivacy.ca/openprivacy/log"
"math/rand"
"os"
"path"
path "path/filepath"
"runtime"
"runtime/pprof"
"testing"
"time"
)
func TestLaunchTor(t *testing.T) {
goRoutineStart := runtime.NumGoroutine()
log.SetLevel(log.LevelDebug)
rand.Seed(int64(time.Now().Nanosecond()))
@ -25,14 +28,23 @@ func TestLaunchTor(t *testing.T) {
t.Fatalf("failed to create torrc file: %v", err)
}
dataDir := ""
if dataDir, err = os.MkdirTemp(path.Join("..", "testing"), "data-dir-"); err != nil {
t.Fatalf("could not create data dir")
}
// Get the current working director, clean the paths to remove relative references
wd, _ := os.Getwd()
t.Logf("Launching bundled tor at %v", path.Clean(wd+"/../tmp/tor"))
acn, err := tor.NewTorACNWithAuth(path.Clean(wd+"/../tmp/data"), path.Clean(wd+"/../tmp/tor"), controlPort, tor.HashedPasswordAuthenticator{Password: password})
acn, err := tor.NewTorACNWithAuth(path.Clean(wd+"/../tmp/data"), path.Clean(wd+"/../tmp/tor"), dataDir, controlPort, tor.HashedPasswordAuthenticator{Password: password})
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 {
@ -41,4 +53,15 @@ func TestLaunchTor(t *testing.T) {
t.Log("we have bootstrapped!")
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,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"

View File

@ -1,4 +0,0 @@
SOCKSPort 9050
ControlPort 9051
# "examplehashedpassword" - used for testing
HashedControlPassword 16:C15305F97789414B601259E3EC5E76B8E55FC56A9F562B713F3D2BA257

View File

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

View File

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

View File

@ -1,6 +1,7 @@
package tor
import (
"bytes"
"context"
"errors"
"fmt"
@ -12,12 +13,11 @@ import (
"git.openprivacy.ca/openprivacy/log"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/sha3"
"io/ioutil"
"net"
"net/textproto"
"os"
"os/exec"
"path"
path "path/filepath"
"regexp"
"strconv"
"strings"
@ -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 {
@ -70,8 +70,13 @@ type torProvider struct {
breakChan chan bool
childListeners map[string]*onionListenService
statusCallback func(int, string)
versionCallback func(string)
lastRestartTime time.Time
authenticator tor.Authenticator
isClosed bool
dataDir string
version string
bootProgress int
}
func (ols *onionListenService) AddressFull() string {
@ -80,27 +85,94 @@ 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()
}
func (tp *torProvider) GetInfo(onion string) (map[string]string, error) {
tp.lock.Lock()
defer tp.lock.Unlock()
circuits, streams, err := getCircuitInfo(tp.t.Control)
if err == nil {
var circuitID string
for _, stream := range streams {
if stream.Key == "stream-status" {
lines := strings.Split(stream.Val, "\n")
for _, line := range lines {
parts := strings.Split(line, " ")
// StreamID SP StreamStatus SP CircuitID SP Target CRLF
if len(parts) == 4 {
if strings.HasPrefix(parts[3], onion) {
circuitID = parts[2]
break
}
}
}
}
}
if circuitID == "" {
return nil, errors.New("could not find circuit")
}
var hops []string
for _, circuit := range circuits {
if circuit.Key == "circuit-status" {
lines := strings.Split(circuit.Val, "\n")
for _, line := range lines {
parts := strings.Split(line, " ")
// CIRCID SP STATUS SP PATH...
if len(parts) >= 3 && circuitID == parts[0] {
//log.Debugf("Found Circuit for Onion %v %v", onion, parts)
if parts[1] == "BUILT" {
circuitPath := strings.Split(parts[2], ",")
for _, hop := range circuitPath {
fingerprint := hop[1:41]
keyvals, err := tp.t.Control.GetInfo(fmt.Sprintf("ns/id/%s", fingerprint))
if err == nil && len(keyvals) == 1 {
lines = strings.Split(keyvals[0].Val, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "r") {
parts := strings.Split(line, " ")
if len(parts) > 6 {
keyvals, err := tp.t.Control.GetInfo(fmt.Sprintf("ip-to-country/%s", parts[6]))
if err == nil && len(keyvals) >= 1 {
hops = append(hops, fmt.Sprintf("%s:%s", strings.ToUpper(keyvals[0].Val), parts[6]))
} else {
hops = append(hops, fmt.Sprintf("%s:%s", "XX", parts[6]))
}
}
}
}
}
}
return map[string]string{"circuit": strings.Join(hops, ",")}, nil
}
}
}
}
}
}
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
// returns -1 on network disconnected
// returns -2 on error
//
// returns -1 on network disconnected
// returns -2 on error
func (tp *torProvider) GetBootstrapStatus() (int, string) {
tp.lock.Lock()
defer tp.lock.Unlock()
@ -126,8 +198,6 @@ func (tp *torProvider) GetBootstrapStatus() (int, string) {
status := ""
if len(kvs) > 0 {
progRe := regexp.MustCompile("PROGRESS=([0-9]*)")
sumRe := regexp.MustCompile("SUMMARY=\"(.*)\"$")
if progMatches := progRe.FindStringSubmatch(kvs[0].Val); len(progMatches) > 1 {
progress, _ = strconv.Atoi(progMatches[1])
@ -137,40 +207,35 @@ func (tp *torProvider) GetBootstrapStatus() (int, string) {
status = statusMatches[1]
}
}
tp.bootProgress = progress
return progress, status
}
func (tp *torProvider) GetVersion() string {
tp.lock.Lock()
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 {
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 +243,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 +253,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
@ -196,7 +268,28 @@ func (tp *torProvider) Listen(identity connectivity.PrivateKey, port int) (conne
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 {
return nil, err
@ -218,10 +311,12 @@ func (tp *torProvider) Listen(identity connectivity.PrivateKey, port int) (conne
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
ols := &onionListenService{os: os, tp: tp}
tp.childListeners[ols.AddressIdentity()] = ols
tp.childListeners[ols.AddressFull()] = ols
return ols, nil
}
@ -229,12 +324,12 @@ func (tp *torProvider) Restart() {
log.Debugf("launching restart...")
tp.lock.Lock()
defer tp.lock.Unlock()
log.Debugf("checking last restart time")
if time.Since(tp.lastRestartTime) < restartCooldown {
tp.lock.Unlock()
return
}
tp.lock.Unlock()
go tp.restart()
}
@ -247,17 +342,23 @@ func (tp *torProvider) restart() {
// preserve status callback after shutdown
statusCallback := tp.statusCallback
versionCallback := tp.versionCallback
tp.t = nil
log.Debugf("Restarting Tor Process")
newTp, err := startTor(tp.appDirectory, tp.bundeledTorPath, tp.controlPort, tp.authenticator)
newTp, err := startTor(tp.appDirectory, tp.bundeledTorPath, tp.dataDir, tp.controlPort, tp.authenticator)
if err == nil {
// we need to reassign tor, dialer and callback which will have changed by swapping out
// the underlying connection.
tp.t = newTp.t
tp.dialer = newTp.dialer
tp.statusCallback = statusCallback
tp.versionCallback = versionCallback
if tp.versionCallback != nil {
tp.versionCallback(tp.version)
}
tp.lastRestartTime = time.Now()
tp.isClosed = false
go tp.monitorRestart()
} else {
log.Errorf("Error restarting Tor process: %v", err)
@ -271,6 +372,10 @@ func (tp *torProvider) Open(hostname string) (net.Conn, string, error) {
tp.lock.Unlock()
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()
resolvedHostname := hostname
@ -278,22 +383,38 @@ func (tp *torProvider) Open(hostname string) (net.Conn, string, error) {
addrParts := strings.Split(hostname, ":")
resolvedHostname = addrParts[1]
}
conn, err := tp.dialer.Dial("tcp", resolvedHostname+".onion:9878")
return conn, resolvedHostname, err
}
func (tp *torProvider) Close() {
for _, child := range tp.childListeners {
child.Close()
}
closing := false
tp.lock.Lock()
defer tp.lock.Unlock()
tp.breakChan <- true
if tp.t != nil {
tp.t.Close()
tp.t = nil
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
for addr, child := range tp.childListeners {
child.Close()
delete(tp.childListeners, addr)
}
log.Debugf("shutting down acn threads..(is already closed: %v)", tp.isClosed)
if closing {
if tp.t != nil {
tp.t.Close()
tp.t = nil
}
}
}
@ -303,28 +424,42 @@ func (tp *torProvider) SetStatusCallback(callback func(int, string)) {
tp.statusCallback = callback
}
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()
defer tp.lock.Unlock()
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) {
tp.lock.Lock()
defer tp.lock.Unlock()
if tp.statusCallback != nil {
tp.statusCallback(prog, status)
}
tp.lock.Unlock()
}
// NewTorACNWithAuth creates/starts a Tor ACN and returns a usable ACN object
func NewTorACNWithAuth(appDirectory string, bundledTorPath string, controlPort int, authenticator tor.Authenticator) (connectivity.ACN, error) {
tp, err := startTor(appDirectory, bundledTorPath, controlPort, authenticator)
func NewTorACNWithAuth(appDirectory string, bundledTorPath string, dataDir string, controlPort int, authenticator tor.Authenticator) (connectivity.ACN, error) {
tp, err := startTor(appDirectory, bundledTorPath, dataDir, controlPort, authenticator)
if err == nil {
tp.isClosed = false
go tp.monitorRestart()
}
return tp, err
}
// NewTorACN creates/starts a Tor ACN and returns a usable ACN object with a NullAuthenticator - this will fail.
func NewTorACN(appDirectory string, bundledTorPath string) (connectivity.ACN, error) {
return NewTorACNWithAuth(appDirectory, bundledTorPath, 9051, NullAuthenticator{})
}
// newHideCmd creates a Creator function for bine which generates a cmd that one windows will hide the dosbox
func newHideCmd(exePath string) process.Creator {
return process.CmdCreatorFunc(func(ctx context.Context, args ...string) (*exec.Cmd, error) {
@ -335,11 +470,19 @@ func newHideCmd(exePath string) process.Creator {
cmd.Stdout = loggerDebug
cmd.Stderr = loggerError
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
})
}
func (tp *torProvider) checkVersion() error {
func (tp *torProvider) checkVersion() (string, error) {
// attempt connect to system tor
log.Debugf("dialing tor control port")
controlport, err := dialControlPort(tp.controlPort)
@ -352,36 +495,32 @@ func (tp *torProvider) checkVersion() error {
if err == nil {
if minTorVersionReqs(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, controlPort int, authenticator tor.Authenticator) (*torProvider, error) {
func startTor(appDirectory string, bundledTorPath string, dataDir string, controlPort int, authenticator tor.Authenticator) (*torProvider, error) {
torDir := path.Join(appDirectory, "tor")
os.MkdirAll(torDir, 0700)
dataDir := ""
var err error
if dataDir, err = ioutil.TempDir(torDir, "data-dir-"); err != nil {
return nil, fmt.Errorf("unable to create temp data dir: %v", err)
}
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")
if err := tp.checkVersion(); err == nil {
if version, err := tp.checkVersion(); err == nil {
tp.version = version
controlport, err := dialControlPort(tp.controlPort)
if err == nil {
log.Debugf("creating tor handler from system tor")
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
@ -391,32 +530,36 @@ func startTor(appDirectory string, bundledTorPath string, controlPort int, authe
return nil, err
}
// if not, try running system tor
log.Debugln("checking if we can run system installed tor or bundled tor")
if checkCmdlineTorVersion("tor") {
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) {
// if not, try bundled tor, then running system tor
log.Debugln("checking if we can run bundled tor or system installed tor")
if version, pass := checkCmdlineTorVersion(bundledTorPath); pass {
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 {
log.Debugf("Error running bundled tor %v\n", err)
return nil, err
}
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 {
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 {
tp.t.DeleteDataDirOnClose = true
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.version = version
tp.t.Control.TakeOwnership()
return tp, err
}
return nil, fmt.Errorf("could not connect to running tor: %v", err)
@ -430,12 +573,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
@ -450,7 +587,6 @@ func (tp *torProvider) monitorRestart() {
prog, status := tp.GetBootstrapStatus()
if prog == torDown && tp.t != nil {
log.Warnf("monitorRestart calling tp.restart() with prog:%v\n", prog)
tp.restart()
return
}
@ -467,13 +603,22 @@ func (tp *torProvider) monitorRestart() {
}
}
func getCircuitInfo(controlport *control.Conn) ([]*control.KeyVal, []*control.KeyVal, error) {
circuits, cerr := controlport.GetInfo("circuit-status")
streams, serr := controlport.GetInfo("stream-status")
if cerr == nil && serr == nil {
return circuits, streams, nil
}
return nil, nil, errors.New("could not fetch circuits or streams")
}
func createFromExisting(controlport *control.Conn, datadir string) *tor.Tor {
t := &tor.Tor{
Process: nil,
Control: controlport,
ProcessCancelFunc: nil,
DataDir: datadir,
DeleteDataDirOnClose: true,
DeleteDataDirOnClose: false,
DebugWriter: nil,
StopProcessOnClose: false,
GeoIPCreatedFile: "",
@ -484,25 +629,57 @@ func createFromExisting(controlport *control.Conn, datadir string) *tor.Tor {
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.SysProcAttr = sysProcAttr
out, err := cmd.CombinedOutput()
re := regexp.MustCompile(`[0-1]\.[0-9]\.[0-9]\.[0-9]`)
sysTorVersion := re.Find(out)
log.Infoln("tor version: " + string(sysTorVersion))
return err == nil && minTorVersionReqs(string(sysTorVersion))
var outb bytes.Buffer
cmd.Stdout = &outb
waiting := make(chan error, 1)
// 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
// min requirement: 0.3.5.x
func minTorVersionReqs(torversion string) bool {
torversions := strings.Split(torversion, ".") //eg: 0.3.4.8 or 0.3.5.1-alpha
log.Debugf("torversions: %v", torversions)
tva, _ := strconv.Atoi(torversions[0])
tvb, _ := strconv.Atoi(torversions[1])
tvc, _ := strconv.Atoi(torversions[2])
return tva > 0 || (tva == 0 && (tvb > 3 || (tvb == 3 && tvc >= 5)))
if len(torversions) >= 3 {
log.Debugf("torversions: %v", torversions)
tva, _ := strconv.Atoi(torversions[0])
tvb, _ := strconv.Atoi(torversions[1])
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) {

View File

@ -3,8 +3,12 @@ package tor
import (
"fmt"
"git.openprivacy.ca/openprivacy/log"
"path"
"os"
path "path/filepath"
"runtime"
"runtime/pprof"
"testing"
"time"
)
func getStatusCallback(progChan chan int) func(int, string) {
@ -14,17 +18,39 @@ 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) {
progChan := make(chan int)
goRoutineStart := runtime.NumGoroutine()
progChan := make(chan int, 10)
verChan := make(chan string, 10)
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"})
dataDir := ""
var err error
if dataDir, err = os.MkdirTemp(path.Join("..", "testing"), "data-dir-"); err != nil {
t.Fatalf("could not create data dir")
}
acn, err := NewTorACNWithAuth(path.Join("../testing/"), torpath, dataDir, 9051, HashedPasswordAuthenticator{"examplehashedpassword"})
if err != nil {
t.Error(err)
return
}
acn.SetStatusCallback(getStatusCallback(progChan))
acn.SetVersionCallback(getVersionCallback(verChan))
progress := 0
for progress < 100 {
@ -39,6 +65,35 @@ func TestTorProvider(t *testing.T) {
progress = <-progChan
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
_, _, err = acn.Open("isbr2t6bflul2zyi6hjtnuezb2xvfr42svzjg2q3gyqfgg3wmnrbkkqd")
if err == nil {
info, err := acn.GetInfo("isbr2t6bflul2zyi6hjtnuezb2xvfr42svzjg2q3gyqfgg3wmnrbkkqd")
if err != nil {
t.Fatalf("could not find info for OP server %v", err)
}
cinfo, exists := info["circuit"]
if !exists || len(cinfo) == 0 {
t.Fatalf("could not find circuit info for OP server %v", err)
}
_, err = acn.GetInfo("not_a_real_onion")
if err == nil {
t.Fatalf("GetInfo for non existent onion should have errored")
}
} else {
t.Fatalf("could not connect to OP server %v", err)
}
// Should skip without blocking...
acn.Restart()
@ -46,4 +101,15 @@ func TestTorProvider(t *testing.T) {
acn.Restart()
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

@ -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)
}
}

View File

@ -6,10 +6,18 @@ import (
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"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
type TorrcBuilder struct {
lines []string
@ -34,6 +42,32 @@ func (tb *TorrcBuilder) WithControlPort(port int) *TorrcBuilder {
return tb
}
// 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.
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
return tb
}
// WithOnionTrafficOnly ensures that the tor process only routes tor onion traffic.
func (tb *TorrcBuilder) WithOnionTrafficOnly() *TorrcBuilder {
for i, line := range tb.lines {
@ -45,6 +79,12 @@ func (tb *TorrcBuilder) WithOnionTrafficOnly() *TorrcBuilder {
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.
func (tb *TorrcBuilder) WithHashedPassword(password string) *TorrcBuilder {
var salt [8]byte
@ -58,7 +98,12 @@ func (tb *TorrcBuilder) WithHashedPassword(password string) *TorrcBuilder {
// Build finalizes the torrc contents and write a file
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.
func (tb *TorrcBuilder) Preview() string {
return strings.Join(tb.lines, "\n")
}
// GenerateHashedPassword calculates a hash in the same way tha tor --hash-password does