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 {
|
for _, val := range resp.Data {
|
||||||
infoVal := &KeyVal{}
|
infoVal := &KeyVal{}
|
||||||
infoVal.Key, infoVal.Val, _ = util.PartitionString(val, '=')
|
infoVal.Key, infoVal.Val, _ = util.PartitionString(val, '=')
|
||||||
|
if infoVal.Val, err = util.UnescapeSimpleQuotedStringIfNeeded(infoVal.Val); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
ret = append(ret, infoVal)
|
ret = append(ret, infoVal)
|
||||||
}
|
}
|
||||||
return ret, nil
|
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.
|
// 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
|
package tor
|
||||||
|
|
12
tor/tor.go
12
tor/tor.go
|
@ -7,6 +7,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -98,6 +99,9 @@ type StartConf struct {
|
||||||
// DebugWriter is the writer to use for debug logs, or nil for no debug
|
// DebugWriter is the writer to use for debug logs, or nil for no debug
|
||||||
// logs.
|
// logs.
|
||||||
DebugWriter io.Writer
|
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()
|
// 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{}
|
conf = &StartConf{}
|
||||||
}
|
}
|
||||||
tor := &Tor{DataDir: conf.DataDir, DebugWriter: conf.DebugWriter, StopProcessOnClose: true}
|
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 == "" {
|
if tor.DataDir == "" {
|
||||||
tempBase := conf.TempDataDirBase
|
tempBase := conf.TempDataDirBase
|
||||||
if tempBase == "" {
|
if tempBase == "" {
|
||||||
tempBase = "."
|
tempBase = "."
|
||||||
}
|
}
|
||||||
var err error
|
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 {
|
if tor.DataDir, err = ioutil.TempDir(tempBase, "data-dir-"); err != nil {
|
||||||
return nil, fmt.Errorf("Unable to create temp data dir: %v", err)
|
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 {
|
if !conf.EnableNetwork {
|
||||||
args = append(args, "--DisableNetwork", "1")
|
args = append(args, "--DisableNetwork", "1")
|
||||||
}
|
}
|
||||||
|
if !conf.NoHush {
|
||||||
|
args = append(args, "--hush")
|
||||||
|
}
|
||||||
// If there is no Torrc file, create a blank temp one
|
// If there is no Torrc file, create a blank temp one
|
||||||
torrcFileName := conf.TorrcFile
|
torrcFileName := conf.TorrcFile
|
||||||
if torrcFileName == "" {
|
if torrcFileName == "" {
|
||||||
|
|
Loading…
Reference in New Issue