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..d8ea076 100644 --- a/tor/torProvider.go +++ b/tor/torProvider.go @@ -91,6 +91,77 @@ 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 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 +} + // 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 +550,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, diff --git a/tor/torProvider_test.go b/tor/torProvider_test.go index e7104a1..3c12575 100644 --- a/tor/torProvider_test.go +++ b/tor/torProvider_test.go @@ -43,6 +43,28 @@ 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 || 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 existant onion should have errored") + } + + } else { + t.Fatalf("could not connect to OP server %v", err) + } + // Should skip without blocking... acn.Restart() acn.Restart()