Add dialing out with example
This commit is contained in:
parent
5cd6cc57ee
commit
0e8472626c
|
@ -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
|
||||
|
|
|
@ -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 ""
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
12
tor/tor.go
12
tor/tor.go
|
@ -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 == "" {
|
||||
|
|
Loading…
Reference in New Issue