From f82cf26731a41d24c5a1b7c1e2912befdf84c3d5 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 13 Jan 2022 13:36:34 -0800 Subject: [PATCH 1/4] Allow Querying of ACN Info - Support Getting Circuit Stats --- acn.go | 2 ++ error_acn.go | 18 +++++++---- localProvider.go | 4 +++ proxy_acn.go | 4 +++ tor/torProvider.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 6 deletions(-) diff --git a/acn.go b/acn.go index 5afabb8..ac03ae0 100644 --- a/acn.go +++ b/acn.go @@ -57,5 +57,7 @@ type ACN interface { Callback() func(int, string) + GetInfo(onion string) (map[string]string, error) + Close() } diff --git a/error_acn.go b/error_acn.go index d9dbb2b..ea035f9 100644 --- a/error_acn.go +++ b/error_acn.go @@ -6,6 +6,8 @@ import ( "net" ) +const acnError = "error initializing anonymous communication network" + // 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 { @@ -16,12 +18,16 @@ func (e ErrorACN) Callback() func(int, string) { return e.statusCallbackCache } +func (e *ErrorACN) GetInfo(addr string) (map[string]string, error) { + return nil, errors.New(acnError) +} + func (e ErrorACN) GetBootstrapStatus() (int, string) { - return -1, "error initializing tor" + return -1, acnError } func (e ErrorACN) WaitTillBootstrapped() error { - return errors.New("error initializing tor") + return errors.New(acnError) } func (e *ErrorACN) SetStatusCallback(callback func(int, string)) { @@ -32,19 +38,19 @@ func (e ErrorACN) Restart() { } func (e ErrorACN) Open(hostname string) (net.Conn, string, error) { - return nil, "", fmt.Errorf("error initializing tor") + return nil, "", fmt.Errorf(acnError) } func (e ErrorACN) Listen(identity PrivateKey, port int) (ListenService, error) { - return nil, fmt.Errorf("error initializing tor") + return nil, fmt.Errorf(acnError) } func (e ErrorACN) GetPID() (int, error) { - return -1, fmt.Errorf("error initializing tor") + return -1, fmt.Errorf(acnError) } func (e ErrorACN) GetVersion() string { - return "Error Initializing Tor" + return acnError } func (e ErrorACN) Close() { diff --git a/localProvider.go b/localProvider.go index 656d0ba..1964a79 100644 --- a/localProvider.go +++ b/localProvider.go @@ -38,6 +38,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" diff --git a/proxy_acn.go b/proxy_acn.go index 0161085..c5ebab7 100644 --- a/proxy_acn.go +++ b/proxy_acn.go @@ -33,6 +33,10 @@ func (p *ProxyACN) ReplaceACN(acn ACN) { 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() } diff --git a/tor/torProvider.go b/tor/torProvider.go index 2fe156c..a69254b 100644 --- a/tor/torProvider.go +++ b/tor/torProvider.go @@ -91,6 +91,73 @@ func (ols *onionListenService) Close() { 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 cirucitID 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) { + cirucitID = parts[2] + break + } + } + } + } + } + + 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 && cirucitID == 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 +} + // 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 @@ -479,6 +546,15 @@ 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, From 0c745e7691113898c23c13d8958912c6e3ebb212 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 13 Jan 2022 13:43:27 -0800 Subject: [PATCH 2/4] Adding Tests for GetInfo --- tor/torProvider.go | 4 ++++ tor/torProvider_test.go | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/tor/torProvider.go b/tor/torProvider.go index a69254b..a43fe32 100644 --- a/tor/torProvider.go +++ b/tor/torProvider.go @@ -114,6 +114,10 @@ func (tp *torProvider) GetInfo(onion string) (map[string]string, error) { } } + if cirucitID == "" { + return nil, errors.New("could not find circuit") + } + var hops []string for _, circuit := range circuits { if circuit.Key == "circuit-status" { diff --git a/tor/torProvider_test.go b/tor/torProvider_test.go index e7104a1..1630bf4 100644 --- a/tor/torProvider_test.go +++ b/tor/torProvider_test.go @@ -43,6 +43,29 @@ func TestTorProvider(t *testing.T) { t.Logf("progress: %v", progress) } + // 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 { + t.Fatalf("could not find circuit info for OP server %v", err) + } + t.Logf("Found Cicurit Info %v", cinfo) + + _, err = acn.GetInfo("not_a_real_onion") + if err == nil { + t.Fatalf("GetInfo for non existant onion should have errored") + } + + } else { + t.Fatalf("could not connect to OP server %v", err) + } + // Should skip without blocking... acn.Restart() acn.Restart() From 4353143ae44436c47b9dff8bc7c0a9e439bd1001 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 13 Jan 2022 13:49:54 -0800 Subject: [PATCH 3/4] Assert CInfo is not empty in Test --- tor/torProvider_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tor/torProvider_test.go b/tor/torProvider_test.go index 1630bf4..3c12575 100644 --- a/tor/torProvider_test.go +++ b/tor/torProvider_test.go @@ -52,10 +52,9 @@ func TestTorProvider(t *testing.T) { t.Fatalf("could not find info for OP server %v", err) } cinfo, exists := info["circuit"] - if !exists { + if !exists || len(cinfo) == 0 { t.Fatalf("could not find circuit info for OP server %v", err) } - t.Logf("Found Cicurit Info %v", cinfo) _, err = acn.GetInfo("not_a_real_onion") if err == nil { From dfae5b9261463df7d7ec677858e7e816af66ca15 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 17 Jan 2022 12:06:36 -0800 Subject: [PATCH 4/4] Spelling --- tor/torProvider.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tor/torProvider.go b/tor/torProvider.go index a43fe32..d8ea076 100644 --- a/tor/torProvider.go +++ b/tor/torProvider.go @@ -97,7 +97,7 @@ func (tp *torProvider) GetInfo(onion string) (map[string]string, error) { circuits, streams, err := getCircuitInfo(tp.t.Control) if err == nil { - var cirucitID string + var circuitID string for _, stream := range streams { if stream.Key == "stream-status" { lines := strings.Split(stream.Val, "\n") @@ -106,7 +106,7 @@ func (tp *torProvider) GetInfo(onion string) (map[string]string, error) { // StreamID SP StreamStatus SP CircuitID SP Target CRLF if len(parts) == 4 { if strings.HasPrefix(parts[3], onion) { - cirucitID = parts[2] + circuitID = parts[2] break } } @@ -114,7 +114,7 @@ func (tp *torProvider) GetInfo(onion string) (map[string]string, error) { } } - if cirucitID == "" { + if circuitID == "" { return nil, errors.New("could not find circuit") } @@ -126,7 +126,7 @@ func (tp *torProvider) GetInfo(onion string) (map[string]string, error) { parts := strings.Split(line, " ") // CIRCID SP STATUS SP PATH... - if len(parts) >= 3 && cirucitID == parts[0] { + 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], ",")