Add dialing out with example

This commit is contained in:
Chad Retz 2018-05-15 16:49:57 -05:00
parent 5cd6cc57ee
commit 0e8472626c
5 changed files with 208 additions and 1 deletions

View File

@ -46,6 +46,9 @@ func (c *Conn) GetInfo(keys ...string) ([]*KeyVal, error) {
for _, val := range resp.Data {
infoVal := &KeyVal{}
infoVal.Key, infoVal.Val, _ = util.PartitionString(val, '=')
if infoVal.Val, err = util.UnescapeSimpleQuotedStringIfNeeded(infoVal.Val); err != nil {
return nil, err
}
ret = append(ret, infoVal)
}
return ret, nil

View File

@ -0,0 +1,68 @@
package main
import (
"bytes"
"context"
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/cretz/bine/tor"
"golang.org/x/net/html"
)
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
// Start tor with default config (can set start conf's DebugWriter to os.Stdout for debug logs)
fmt.Println("Starting tor and fetching title of https://check.torproject.org, please wait a few seconds...")
t, err := tor.Start(nil, nil)
if err != nil {
return err
}
defer t.Close()
// Wait at most a minute to start network and get
dialCtx, dialCancel := context.WithTimeout(context.Background(), time.Minute)
defer dialCancel()
// Make connection
dialer, err := t.Dialer(dialCtx, nil)
if err != nil {
return err
}
httpClient := &http.Client{Transport: &http.Transport{DialContext: dialer.DialContext}}
// Get /
resp, err := httpClient.Get("https://check.torproject.org")
if err != nil {
return err
}
defer resp.Body.Close()
// Grab the <title>
parsed, err := html.Parse(resp.Body)
if err != nil {
return err
}
fmt.Printf("Title: %v\n", getTitle(parsed))
return nil
}
func getTitle(n *html.Node) string {
if n.Type == html.ElementNode && n.Data == "title" {
var title bytes.Buffer
if err := html.Render(&title, n.FirstChild); err != nil {
panic(err)
}
return strings.TrimSpace(title.String())
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
if title := getTitle(c); title != "" {
return title
}
}
return ""
}

121
tor/dialer.go Normal file
View File

@ -0,0 +1,121 @@
package tor
import (
"context"
"fmt"
"net"
"strings"
"golang.org/x/net/proxy"
)
// Dialer is a wrapper around a proxy.Dialer for dialing connections.
type Dialer struct {
proxy.Dialer
}
// DialConf is the configuration used for Dialer.
type DialConf struct {
// ProxyAddress is the address for the SOCKS5 proxy. If empty, it is looked
// up.
ProxyAddress string
// ProxyNetwork is the network for the SOCKS5 proxy. If ProxyAddress is
// empty, this value is ignored and overridden by what is looked up. If this
// is empty and ProxyAddress is not empty, it defaults to "tcp".
ProxyNetwork string
// ProxyAuth is the auth for the proxy. An empty auth means no auth, a nil
// auth means it is looked up.
ProxyAuth *proxy.Auth
// SkipEnableNetwork, if true, will skip the enable network step in Dialer.
SkipEnableNetwork bool
// Forward is the dialer to forward to. If nil, just uses normal net dialer.
Forward proxy.Dialer
}
// Dialer creates a new Dialer for the given configuration. Context can be nil.
// If conf is nil, a default is used.
func (t *Tor) Dialer(ctx context.Context, conf *DialConf) (*Dialer, error) {
if ctx == nil {
ctx = context.Background()
}
if conf == nil {
conf = &DialConf{}
}
// Enable the network if requested
if !conf.SkipEnableNetwork {
if err := t.EnableNetwork(ctx, true); err != nil {
return nil, err
}
}
// Lookup proxy address as needed
proxyNetwork := conf.ProxyNetwork
proxyAddress := conf.ProxyAddress
if proxyAddress == "" {
info, err := t.Control.GetInfo("net/listeners/socks")
if err != nil {
return nil, err
}
if len(info) != 1 || info[0].Key != "net/listeners/socks" {
return nil, fmt.Errorf("Unable to get socks proxy address")
}
proxyAddress = info[0].Val
if strings.HasPrefix(proxyAddress, "unix:") {
proxyAddress = proxyAddress[5:]
proxyNetwork = "unix"
} else {
proxyNetwork = "tcp"
}
} else if proxyNetwork == "" {
proxyNetwork = "tcp"
}
// Lookup proxy auth as needed
proxyAuth := conf.ProxyAuth
if proxyAuth == nil {
info, err := t.Control.GetConf("Socks5ProxyUsername", "Socks5ProxyPassword")
if err != nil {
return nil, err
}
if len(info) != 2 || info[0].Key != "Socks5ProxyUsername" || info[1].Key != "Socks5ProxyPassword" {
return nil, fmt.Errorf("Unable to get proxy auth")
}
proxyAuth = &proxy.Auth{User: info[0].Val, Password: info[1].Val}
}
if proxyAuth.User == "" && proxyAuth.Password == "" {
proxyAuth = nil
}
dialer, err := proxy.SOCKS5(proxyNetwork, proxyAddress, proxyAuth, conf.Forward)
if err != nil {
return nil, err
}
return &Dialer{dialer}, nil
}
// DialContext is the equivalent of net.DialContext.
//
// TODO: Remove when https://github.com/golang/go/issues/17759 is released.
func (d *Dialer) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) {
errCh := make(chan error, 1)
connCh := make(chan net.Conn, 1)
go func() {
if conn, err := d.Dial(network, addr); err != nil {
errCh <- err
} else if ctx.Err() != nil {
conn.Close()
} else {
connCh <- conn
}
}()
select {
case err := <-errCh:
return nil, err
case conn := <-connCh:
return conn, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}

View File

@ -1,2 +1,7 @@
// Package tor is the high-level client for Tor.
//
// The Tor type is a combination of a Tor instance and a connection to it.
// Use Start to create Tor. Then Dialer or Listener can be used.
//
// Some of this code is lifted from https://github.com/yawning/bulb with thanks.
package tor

View File

@ -7,6 +7,7 @@ import (
"io/ioutil"
"net/textproto"
"os"
"path/filepath"
"strconv"
"time"
@ -98,6 +99,9 @@ type StartConf struct {
// DebugWriter is the writer to use for debug logs, or nil for no debug
// logs.
DebugWriter io.Writer
// NoHush if true does not set --hush. By default --hush is set.
NoHush bool
}
// Start a Tor instance and connect to it. If ctx is nil, context.Background()
@ -110,13 +114,16 @@ func Start(ctx context.Context, conf *StartConf) (*Tor, error) {
conf = &StartConf{}
}
tor := &Tor{DataDir: conf.DataDir, DebugWriter: conf.DebugWriter, StopProcessOnClose: true}
// Create the data dir
// Create the data dir and make it absolute
if tor.DataDir == "" {
tempBase := conf.TempDataDirBase
if tempBase == "" {
tempBase = "."
}
var err error
if tempBase, err = filepath.Abs(tempBase); err != nil {
return nil, err
}
if tor.DataDir, err = ioutil.TempDir(tempBase, "data-dir-"); err != nil {
return nil, fmt.Errorf("Unable to create temp data dir: %v", err)
}
@ -165,6 +172,9 @@ func (t *Tor) startProcess(ctx context.Context, conf *StartConf) error {
if !conf.EnableNetwork {
args = append(args, "--DisableNetwork", "1")
}
if !conf.NoHush {
args = append(args, "--hush")
}
// If there is no Torrc file, create a blank temp one
torrcFileName := conf.TorrcFile
if torrcFileName == "" {