Add ephemeral onion service functionality to controller API
here we also: - add a TorDialer and OnionListener - complete working example of an onion echo server
This commit is contained in:
parent
8f9a7ec076
commit
2beeb7bc51
|
@ -0,0 +1,62 @@
|
||||||
|
// cmd_onion.go - various onion service commands: ADD_ONION, DEL_ONION...
|
||||||
|
//
|
||||||
|
// To the extent possible under law, David Stainton waived all copyright
|
||||||
|
// and related or neighboring rights to this module of bulb, using the creative
|
||||||
|
// commons "cc0" public domain dedication. See LICENSE or
|
||||||
|
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
|
||||||
|
|
||||||
|
package bulb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OnionInfo is the result of the AddOnion command.
|
||||||
|
type OnionInfo struct {
|
||||||
|
OnionId string
|
||||||
|
KeyType string
|
||||||
|
Key string
|
||||||
|
|
||||||
|
RawResponse *Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddOnion issues an ADD_ONION command and returns the parsed response.
|
||||||
|
func (c *Conn) AddOnion(virtPort int, target, keyType, keyContent string, new bool) (*OnionInfo, error) {
|
||||||
|
var fields []string
|
||||||
|
request := "ADD_ONION "
|
||||||
|
onionInfo := OnionInfo{}
|
||||||
|
|
||||||
|
if new {
|
||||||
|
request += "NEW:BEST"
|
||||||
|
} else {
|
||||||
|
request += fmt.Sprintf("%s:%s", keyType, keyContent)
|
||||||
|
}
|
||||||
|
request += fmt.Sprintf(" Port=%d,%s\n", virtPort, target)
|
||||||
|
fmt.Printf("DEBUG request: %s\n", request)
|
||||||
|
response, err := c.Request(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
onionInfo.RawResponse = response
|
||||||
|
fields = strings.Split(fmt.Sprintf("%s", response.Data), "ServiceID=")
|
||||||
|
fields = strings.Split(fields[1], " ")
|
||||||
|
onionInfo.OnionId = fields[0]
|
||||||
|
|
||||||
|
if new {
|
||||||
|
fields = strings.Split(fmt.Sprintf("%s", response.Data), "PrivateKey=")
|
||||||
|
fields = strings.Split(fields[1], ":")
|
||||||
|
onionInfo.KeyType = fields[0]
|
||||||
|
fields = strings.Split(fields[1], "\n")
|
||||||
|
onionInfo.Key = fields[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &onionInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) DeleteOnion(serviceId string) error {
|
||||||
|
var deleteCmd string = fmt.Sprintf("DEL_ONION %s\n", serviceId)
|
||||||
|
_, err := c.Request(deleteCmd)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
// Onion example.
|
||||||
|
//
|
||||||
|
// To the extent possible under law, David Stainton waived all copyright
|
||||||
|
// and related or neighboring rights to this bulb source file, using the creative
|
||||||
|
// commons "cc0" public domain dedication. See LICENSE or
|
||||||
|
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/yawning/bulb"
|
||||||
|
"github.com/yawning/bulb/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Connect to a running tor instance.
|
||||||
|
// unix domain socket - c, err := bulb.Dial("unix", "/var/run/tor/control")
|
||||||
|
c, err := bulb.Dial("tcp4", "127.0.0.1:9051")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to connect to control port: %v", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
// See what's really going on under the hood.
|
||||||
|
// Do not enable in production.
|
||||||
|
c.Debug(true)
|
||||||
|
|
||||||
|
// Authenticate with the control port. The password argument
|
||||||
|
// here can be "" if no password is set (CookieAuth, no auth).
|
||||||
|
if err := c.Authenticate("ExamplePassword"); err != nil {
|
||||||
|
log.Fatalf("Authentication failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
options := utils.OnionListenerOptions{
|
||||||
|
OnionKeyFile: "echoOnionKey",
|
||||||
|
OnionServicePort: 80,
|
||||||
|
LocalAddr: "127.0.0.1:8080",
|
||||||
|
ControlNetwork: "tcp",
|
||||||
|
ControlAddr: "127.0.0.1:9051",
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := utils.NewOnionListener(&options)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := listener.Addr()
|
||||||
|
onionAddr := addr.String()
|
||||||
|
fmt.Printf("onion echo server: listening to %s\n", onionAddr)
|
||||||
|
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Wait for a connection.
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the connection in a new goroutine.
|
||||||
|
// The loop then returns to accepting, so that
|
||||||
|
// multiple connections may be served concurrently.
|
||||||
|
go func(c net.Conn) {
|
||||||
|
// Echo all incoming data.
|
||||||
|
io.Copy(c, c)
|
||||||
|
// Shut down the connection.
|
||||||
|
c.Close()
|
||||||
|
}(conn)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,206 @@
|
||||||
|
// connection.go - A TorDialer and OnionListener...
|
||||||
|
//
|
||||||
|
// To the extent possible under law, David Stainton waived all copyright
|
||||||
|
// and related or neighboring rights to this bulb source file, using the creative
|
||||||
|
// commons "cc0" public domain dedication. See LICENSE or
|
||||||
|
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
|
||||||
|
|
||||||
|
// Package utils implements useful utilities for dealing with Tor and it's
|
||||||
|
// control port.
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yawning/bulb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TorDialer struct {
|
||||||
|
dialer proxy.Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTorDialer(network string, addr string, auth *proxy.Auth) (*TorDialer, error) {
|
||||||
|
var err error
|
||||||
|
var socksDialer proxy.Dialer
|
||||||
|
forwardDialer := net.Dialer{}
|
||||||
|
|
||||||
|
socksDialer, err = proxy.SOCKS5(network, addr, auth, &forwardDialer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("TorDialer: Failed to create socks dialer: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
torDialer := TorDialer{
|
||||||
|
dialer: socksDialer,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &torDialer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TorDialer) Dial(network, addr string) (net.Conn, error) {
|
||||||
|
conn, err := t.dialer.Dial(network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("TorDialer: failed to dial tor socks port: %s\n", err)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type OnionAddr struct {
|
||||||
|
network string
|
||||||
|
address string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OnionAddr) Network() string {
|
||||||
|
return o.network
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OnionAddr) String() string {
|
||||||
|
return o.address
|
||||||
|
}
|
||||||
|
|
||||||
|
type OnionListenerOptions struct {
|
||||||
|
// OnionKeyFile is used to persist onion service key material
|
||||||
|
OnionKeyFile string
|
||||||
|
|
||||||
|
// OnionServicePort is the virtport of the tor onion service
|
||||||
|
OnionServicePort int
|
||||||
|
// LocalAddr is the address of the local TCP Listener
|
||||||
|
LocalAddr string
|
||||||
|
|
||||||
|
// ControlAddr is the Tor control port address
|
||||||
|
ControlAddr string
|
||||||
|
// ControlNetwork is the network type of the Tor control port
|
||||||
|
ControlNetwork string
|
||||||
|
ControlPassword string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OnionListener struct {
|
||||||
|
options *OnionListenerOptions
|
||||||
|
controller *bulb.Conn
|
||||||
|
onionInfo *bulb.OnionInfo
|
||||||
|
localListener net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOnionListener(options *OnionListenerOptions) (*OnionListener, error) {
|
||||||
|
listener := OnionListener{
|
||||||
|
options: options,
|
||||||
|
}
|
||||||
|
if options.ControlAddr == "" || options.ControlNetwork == "" {
|
||||||
|
return nil, fmt.Errorf("Tor control port address not specified.")
|
||||||
|
}
|
||||||
|
err := listener.initOnion()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &listener, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnionListener) controlAuth() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
o.controller, err = bulb.Dial(o.options.ControlNetwork, o.options.ControlAddr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("OnionListener: Failed to connect to Tor control port: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = o.controller.Authenticate(o.options.ControlPassword)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("OnionListener: Failed authenticate with Tor control port: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readOnionKeyFile returns the key type string, key content string and nil error
|
||||||
|
// ...otherwise error will be non-nil.
|
||||||
|
func (o *OnionListener) readOnionKeyFile() (string, string, error) {
|
||||||
|
var fields []string
|
||||||
|
onionKeyBytes := make([]byte, 100)
|
||||||
|
onionKeyBytes, err := ioutil.ReadFile(o.options.OnionKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
fields = strings.Split(string(onionKeyBytes), ":")
|
||||||
|
return fields[0], fields[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnionListener) writeOnionKeyFile(keyType, keyContent string) error {
|
||||||
|
keyData := fmt.Sprintf("%s:%s", keyType, keyContent)
|
||||||
|
err := ioutil.WriteFile(o.options.OnionKeyFile, []byte(keyData), 0644)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnionListener) initOnion() error {
|
||||||
|
//var serviceId string
|
||||||
|
var keyType, keyContent string
|
||||||
|
var err error
|
||||||
|
onionKeyContent := ""
|
||||||
|
onionKeyType := ""
|
||||||
|
|
||||||
|
if o.options.OnionKeyFile != "" {
|
||||||
|
onionKeyType, onionKeyContent, err = o.readOnionKeyFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = o.controlAuth()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if onionKeyType != "" {
|
||||||
|
o.onionInfo, err = o.controller.AddOnion(o.options.OnionServicePort, o.options.LocalAddr, onionKeyType, onionKeyContent, false)
|
||||||
|
} else {
|
||||||
|
o.onionInfo, err = o.controller.AddOnion(o.options.OnionServicePort, o.options.LocalAddr, "", "", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("OnionListener: create hidden service fail: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.options.OnionKeyFile != "" {
|
||||||
|
err = o.writeOnionKeyFile(keyType, keyContent)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("OnionListener: failed to write key file to disk: %s\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
o.localListener, err = net.Listen("tcp", o.options.LocalAddr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("OnionListener: local TCP listen error: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnionListener) Accept() (net.Conn, error) {
|
||||||
|
var conn net.Conn
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if o.onionInfo.OnionId == "" {
|
||||||
|
return nil, fmt.Errorf("OnionListener: onion service not initialized.\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err = o.localListener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("OnionListener: local TCP connection Accept failure: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnionListener) Close() error {
|
||||||
|
err := o.controller.DeleteOnion(o.onionInfo.OnionId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("OnionListener: DeleteHiddenService failure: %s\n", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OnionListener) Addr() net.Addr {
|
||||||
|
return OnionAddr{
|
||||||
|
network: "tor",
|
||||||
|
address: fmt.Sprintf("%s.onion:%d", o.onionInfo.OnionId, o.options.OnionServicePort),
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue