Merge pull request #1516 from wpaulino/auto-tor-v3
tor+server: add automatic support for v3 onion services
This commit is contained in:
commit
4dd4f7cf45
47
config.go
47
config.go
@ -57,6 +57,7 @@ const (
|
||||
defaultTorDNSPort = 53
|
||||
defaultTorControlPort = 9051
|
||||
defaultTorV2PrivateKeyFilename = "v2_onion_private_key"
|
||||
defaultTorV3PrivateKeyFilename = "v3_onion_private_key"
|
||||
|
||||
defaultBroadcastDelta = 10
|
||||
|
||||
@ -86,10 +87,9 @@ var (
|
||||
defaultBitcoindDir = btcutil.AppDataDir("bitcoin", false)
|
||||
defaultLitecoindDir = btcutil.AppDataDir("litecoin", false)
|
||||
|
||||
defaultTorSOCKS = net.JoinHostPort("localhost", strconv.Itoa(defaultTorSOCKSPort))
|
||||
defaultTorDNS = net.JoinHostPort(defaultTorDNSHost, strconv.Itoa(defaultTorDNSPort))
|
||||
defaultTorControl = net.JoinHostPort("localhost", strconv.Itoa(defaultTorControlPort))
|
||||
defaultTorV2PrivateKeyPath = filepath.Join(defaultLndDir, defaultTorV2PrivateKeyFilename)
|
||||
defaultTorSOCKS = net.JoinHostPort("localhost", strconv.Itoa(defaultTorSOCKSPort))
|
||||
defaultTorDNS = net.JoinHostPort(defaultTorDNSHost, strconv.Itoa(defaultTorDNSPort))
|
||||
defaultTorControl = net.JoinHostPort("localhost", strconv.Itoa(defaultTorControlPort))
|
||||
)
|
||||
|
||||
type chainConfig struct {
|
||||
@ -148,14 +148,14 @@ type autoPilotConfig struct {
|
||||
}
|
||||
|
||||
type torConfig struct {
|
||||
Active bool `long:"active" description:"Allow outbound and inbound connections to be routed through Tor"`
|
||||
SOCKS string `long:"socks" description:"The host:port that Tor's exposed SOCKS5 proxy is listening on"`
|
||||
DNS string `long:"dns" description:"The DNS server as host:port that Tor will use for SRV queries - NOTE must have TCP resolution enabled"`
|
||||
StreamIsolation bool `long:"streamisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection."`
|
||||
Control string `long:"control" description:"The host:port that Tor is listening on for Tor control connections"`
|
||||
V2 bool `long:"v2" description:"Automatically set up a v2 onion service to listen for inbound connections"`
|
||||
V2PrivateKeyPath string `long:"v2privatekeypath" description:"The path to the private key of the onion service being created"`
|
||||
V3 bool `long:"v3" description:"Use a v3 onion service to listen for inbound connections"`
|
||||
Active bool `long:"active" description:"Allow outbound and inbound connections to be routed through Tor"`
|
||||
SOCKS string `long:"socks" description:"The host:port that Tor's exposed SOCKS5 proxy is listening on"`
|
||||
DNS string `long:"dns" description:"The DNS server as host:port that Tor will use for SRV queries - NOTE must have TCP resolution enabled"`
|
||||
StreamIsolation bool `long:"streamisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection."`
|
||||
Control string `long:"control" description:"The host:port that Tor is listening on for Tor control connections"`
|
||||
V2 bool `long:"v2" description:"Automatically set up a v2 onion service to listen for inbound connections"`
|
||||
V3 bool `long:"v3" description:"Automatically set up a v3 onion service to listen for inbound connections"`
|
||||
PrivateKeyPath string `long:"privatekeypath" description:"The path to the private key of the onion service being created"`
|
||||
}
|
||||
|
||||
// config defines the configuration options for lnd.
|
||||
@ -304,10 +304,9 @@ func loadConfig() (*config, error) {
|
||||
Color: defaultColor,
|
||||
MinChanSize: int64(minChanFundingSize),
|
||||
Tor: &torConfig{
|
||||
SOCKS: defaultTorSOCKS,
|
||||
DNS: defaultTorDNS,
|
||||
Control: defaultTorControl,
|
||||
V2PrivateKeyPath: defaultTorV2PrivateKeyPath,
|
||||
SOCKS: defaultTorSOCKS,
|
||||
DNS: defaultTorDNS,
|
||||
Control: defaultTorControl,
|
||||
},
|
||||
net: &tor.ClearNet{},
|
||||
}
|
||||
@ -363,7 +362,6 @@ func loadConfig() (*config, error) {
|
||||
cfg.TLSCertPath = filepath.Join(lndDir, defaultTLSCertFilename)
|
||||
cfg.TLSKeyPath = filepath.Join(lndDir, defaultTLSKeyFilename)
|
||||
cfg.LogDir = filepath.Join(lndDir, defaultLogDirname)
|
||||
cfg.Tor.V2PrivateKeyPath = filepath.Join(lndDir, defaultTorV2PrivateKeyFilename)
|
||||
}
|
||||
|
||||
// Create the lnd directory if it doesn't already exist.
|
||||
@ -399,7 +397,7 @@ func loadConfig() (*config, error) {
|
||||
cfg.LtcdMode.Dir = cleanAndExpandPath(cfg.LtcdMode.Dir)
|
||||
cfg.BitcoindMode.Dir = cleanAndExpandPath(cfg.BitcoindMode.Dir)
|
||||
cfg.LitecoindMode.Dir = cleanAndExpandPath(cfg.LitecoindMode.Dir)
|
||||
cfg.Tor.V2PrivateKeyPath = cleanAndExpandPath(cfg.Tor.V2PrivateKeyPath)
|
||||
cfg.Tor.PrivateKeyPath = cleanAndExpandPath(cfg.Tor.PrivateKeyPath)
|
||||
|
||||
// Ensure that the user didn't attempt to specify negative values for
|
||||
// any of the autopilot params.
|
||||
@ -490,6 +488,19 @@ func loadConfig() (*config, error) {
|
||||
cfg.DisableListen = true
|
||||
}
|
||||
|
||||
if cfg.Tor.PrivateKeyPath == "" {
|
||||
switch {
|
||||
case cfg.Tor.V2:
|
||||
cfg.Tor.PrivateKeyPath = filepath.Join(
|
||||
lndDir, defaultTorV2PrivateKeyFilename,
|
||||
)
|
||||
case cfg.Tor.V3:
|
||||
cfg.Tor.PrivateKeyPath = filepath.Join(
|
||||
lndDir, defaultTorV3PrivateKeyFilename,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Set up the network-related functions that will be used throughout
|
||||
// the daemon. We use the standard Go "net" package functions by
|
||||
// default. If we should be proxying all traffic through Tor, then
|
||||
|
@ -15,19 +15,14 @@ advertised IP address. Additionally, leaf nodes can also protect their location
|
||||
by using Tor for anonymous networking to establish connections.
|
||||
|
||||
With widespread usage of Onion Services within the network, concerns about the
|
||||
difficulty of proper NAT traversal are alleviated, as usage of Onion Services
|
||||
allows nodes to accept inbound connections even if they're behind a NAT.
|
||||
difficulty of proper NAT traversal are alleviated, as usage of onion services
|
||||
allows nodes to accept inbound connections even if they're behind a NAT. At the
|
||||
time of writing this documentation, `lnd` supports both types of onion services:
|
||||
v2 and v3.
|
||||
|
||||
At the time of writing this documentation, `lnd` supports both types of onion
|
||||
services: v2 and v3. However, only v2 onion services can automatically be
|
||||
created and set up by `lnd` until Tor Control support for v3 onion services is
|
||||
implemented in the stable release of the Tor daemon. v3 onion services can be
|
||||
used as long as they are set up manually. We'll cover the steps on how to do
|
||||
these things below.
|
||||
|
||||
Before following the remainder of this documentation, you should ensure that
|
||||
you already have Tor installed locally. Official instructions to install the
|
||||
latest release of Tor can be found
|
||||
Before following the remainder of this documentation, you should ensure that you
|
||||
already have Tor installed locally. Official instructions to install the latest
|
||||
release of Tor can be found
|
||||
[here](https://www.torproject.org/docs/tor-doc-unix.html.en).
|
||||
|
||||
**NOTE**: This documentation covers how to ensure that `lnd`'s _Lightning
|
||||
@ -80,13 +75,13 @@ At this point, we can now start `lnd` with the relevant arguments:
|
||||
|
||||
Tor:
|
||||
--tor.active Allow outbound and inbound connections to be routed through Tor
|
||||
--tor.socks= The port that Tor's exposed SOCKS5 proxy is listening on -- NOTE port must be between 1024 and 65535 (default: 9050)
|
||||
--tor.dns= The DNS server as IP:PORT that Tor will use for SRV queries - NOTE must have TCP resolution enabled (default: soa.nodes.lightning.directory:53)
|
||||
--tor.socks= The host:port that Tor's exposed SOCKS5 proxy is listening on (default: localhost:9050)
|
||||
--tor.dns= The DNS server as host:port that Tor will use for SRV queries - NOTE must have TCP resolution enabled (default: soa.nodes.lightning.directory:53)
|
||||
--tor.streamisolation Enable Tor stream isolation by randomizing user credentials for each connection.
|
||||
--tor.controlport= The port that Tor is listening on for Tor control connections -- NOTE port must be between 1024 and 65535 (default: 9051)
|
||||
--tor.control= The host:port that Tor is listening on for Tor control connections (default: localhost:9051)
|
||||
--tor.v2 Automatically set up a v2 onion service to listen for inbound connections
|
||||
--tor.v3 Use a v3 onion service to listen for inbound connections
|
||||
--tor.privatekeypath= The path to the private key of the onion service being created (default: /Users/user/Library/Application Support/Lnd/onion_private_key)
|
||||
--tor.v3 Automatically set up a v3 onion service to listen for inbound connections
|
||||
--tor.privatekeypath= The path to the private key of the onion service being created
|
||||
```
|
||||
|
||||
There are a couple things here, so let's dissect them. The `--tor.active` flag
|
||||
@ -101,25 +96,27 @@ queries over Tor. So instead, we need to connect directly to the authoritative
|
||||
DNS server over TCP, in order query for `SRV` records that we can use to
|
||||
bootstrap our connections.
|
||||
|
||||
Inbound connections are possible due to `lnd` automatically creating a v2 onion
|
||||
Inbound connections are possible due to `lnd` automatically creating an onion
|
||||
service. A path to save the onion service's private key can be specified with
|
||||
the `--tor.privatekeypath` flag. A v3 onion service can also be used, but it
|
||||
must be created manually. We'll expand on how this works in [Listening for
|
||||
Inbound Connections](#listening-for-inbound-connections).
|
||||
the `--tor.privatekeypath` flag.
|
||||
|
||||
Most of these arguments have defaults, so as long as they apply to you, routing
|
||||
all outbound and inbound connections through Tor can simply be done with:
|
||||
all outbound and inbound connections through Tor can simply be done with either
|
||||
v2 or v3 onion services:
|
||||
```shell
|
||||
⛰ ./lnd --tor.active --tor.v2
|
||||
```
|
||||
```shell
|
||||
⛰ ./lnd --tor.active --tor.v3
|
||||
```
|
||||
|
||||
Outbound support only can also be used with:
|
||||
```shell
|
||||
⛰ ./lnd --tor.active
|
||||
```
|
||||
|
||||
This will allow you to make all outgoing connections over Tor, but still allow
|
||||
regular (clearnet) incoming connections.
|
||||
This will allow you to make all outgoing connections over Tor. Listening is
|
||||
disabled to prevent inadvertent leaks.
|
||||
|
||||
## Tor Stream Isolation
|
||||
|
||||
@ -138,50 +135,24 @@ specification of an additional argument:
|
||||
## Listening for Inbound Connections
|
||||
|
||||
In order to listen for inbound connections through Tor, an onion service must be
|
||||
created. There are two types of onion services: v2 and v3.
|
||||
created. There are two types of onion services: v2 and v3. v3 onion services
|
||||
are the latest generation of onion services and they provide a number of
|
||||
advantages over the legacy v2 onion services. To learn more about these
|
||||
benefits, see [Intro to Next Gen Onion Services](https://trac.torproject.org/projects/tor/wiki/doc/NextGenOnions).
|
||||
|
||||
### v2 Onion Services
|
||||
Both types can be created and used automatically by `lnd`. Specifying which type
|
||||
should be used can easily be done by either using the `tor.v2` or `tor.v3` flag.
|
||||
|
||||
v2 onion services can be created automatically by `lnd` and are currently the
|
||||
default. To do so, run `lnd` with the following arguments:
|
||||
For example, v3 onion services can be used with the following flags:
|
||||
```
|
||||
⛰ ./lnd --tor.active --tor.v2
|
||||
⛰ ./lnd --tor.active --tor.v3
|
||||
```
|
||||
|
||||
This will automatically create a hidden service for your node to use to listen
|
||||
for inbound connections and advertise itself to the network. The onion service's
|
||||
private key is saved to a file named `onion_private_key` in `lnd`'s base
|
||||
directory. This will allow `lnd` to recreate the same hidden service upon
|
||||
private key is saved to a file named `v2_onion_private_key` or
|
||||
`v3_onion_private_key` depending on the type of onion service used in `lnd`'s
|
||||
base directory. This will allow `lnd` to recreate the same hidden service upon
|
||||
restart. If you wish to generate a new onion service, you can simply delete this
|
||||
file. The path to this private key file can also be modified with the
|
||||
`--tor.privatekeypath` argument.
|
||||
|
||||
### v3 Onion Services
|
||||
|
||||
v3 onion services are the latest generation of onion services and they provide a
|
||||
number of advantages over the legacy v2 onion services. To learn more about
|
||||
these benefits, see [Intro to Next Gen Onion Services](https://trac.torproject.org/projects/tor/wiki/doc/NextGenOnions).
|
||||
|
||||
Unfortunately, at the time of writing this, v3 onion service support is still
|
||||
at an alpha level in the Tor daemon, so we're unable to automatically set them
|
||||
up within `lnd` unlike with v2 onion services. However, they can still be run
|
||||
manually! To do so, append the following lines to the torrc sample from above:
|
||||
```
|
||||
HiddenServiceDir PATH_TO_HIDDEN_SERVICE
|
||||
HiddenServiceVersion 3
|
||||
HiddenServicePort PORT_ONION_SERVICE_LISTENS_ON ADDRESS_LND_LISTENS_ON
|
||||
```
|
||||
|
||||
If needed, instructions on how to set up a v3 onion service manually can be
|
||||
found [here](https://trac.torproject.org/projects/tor/wiki/doc/NextGenOnions#Howtosetupyourownprop224service).
|
||||
|
||||
Once the v3 onion service is set up, `lnd` is able to use it to listen for
|
||||
inbound connections. You'll also need the onion service's hostname in order to
|
||||
advertise your node to the network. To do so, run `lnd` with the following
|
||||
arguments:
|
||||
```
|
||||
⛰ ./lnd --tor.active --tor.v3 --externalip=ONION_SERVICE_HOSTNAME
|
||||
```
|
||||
|
||||
Once v3 onion service support is stable, `lnd` will be updated to also
|
||||
automatically set up v3 onion services.
|
||||
|
43
server.go
43
server.go
@ -432,10 +432,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
||||
// If we were requested to route connections through Tor and to
|
||||
// automatically create an onion service, we'll initiate our Tor
|
||||
// controller and establish a connection to the Tor server.
|
||||
//
|
||||
// NOTE: v3 onion services cannot be created automatically yet. In the
|
||||
// future, this will be expanded to do so.
|
||||
if cfg.Tor.Active && cfg.Tor.V2 {
|
||||
if cfg.Tor.Active {
|
||||
s.torController = tor.NewController(cfg.Tor.Control)
|
||||
}
|
||||
|
||||
@ -1479,34 +1476,36 @@ func (s *server) initTorController() error {
|
||||
// Determine the different ports the server is listening on. The onion
|
||||
// service's virtual port will map to these ports and one will be picked
|
||||
// at random when the onion service is being accessed.
|
||||
listenPorts := make(map[int]struct{})
|
||||
listenPorts := make([]int, 0, len(s.listenAddrs))
|
||||
for _, listenAddr := range s.listenAddrs {
|
||||
// At this point, the listen addresses should have already been
|
||||
// normalized, so it's safe to ignore the errors.
|
||||
_, portStr, _ := net.SplitHostPort(listenAddr.String())
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
listenPorts[port] = struct{}{}
|
||||
port := listenAddr.(*net.TCPAddr).Port
|
||||
listenPorts = append(listenPorts, port)
|
||||
}
|
||||
|
||||
// Once the port mapping has been set, we can go ahead and automatically
|
||||
// create our onion service. The service's private key will be saved to
|
||||
// disk in order to regain access to this service when restarting `lnd`.
|
||||
virtToTargPorts := tor.VirtToTargPorts{defaultPeerPort: listenPorts}
|
||||
onionServiceAddrs, err := s.torController.AddOnionV2(
|
||||
cfg.Tor.V2PrivateKeyPath, virtToTargPorts,
|
||||
)
|
||||
onionCfg := tor.AddOnionConfig{
|
||||
VirtualPort: defaultPeerPort,
|
||||
TargetPorts: listenPorts,
|
||||
PrivateKeyPath: cfg.Tor.PrivateKeyPath,
|
||||
}
|
||||
|
||||
switch {
|
||||
case cfg.Tor.V2:
|
||||
onionCfg.Type = tor.V2
|
||||
case cfg.Tor.V3:
|
||||
onionCfg.Type = tor.V3
|
||||
}
|
||||
|
||||
addr, err := s.torController.AddOnion(onionCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now that the onion service has been created, we'll add the different
|
||||
// onion addresses it can be reached at to our list of advertised
|
||||
// addresses.
|
||||
for _, addr := range onionServiceAddrs {
|
||||
s.currentNodeAnn.Addresses = append(
|
||||
s.currentNodeAnn.Addresses, addr,
|
||||
)
|
||||
}
|
||||
// Now that the onion service has been created, we'll add the onion
|
||||
// address it can be reached at to our list of advertised addresses.
|
||||
s.currentNodeAnn.Addresses = append(s.currentNodeAnn.Addresses, addr)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
@ -30,6 +31,11 @@ const (
|
||||
// ProtocolInfoVersion is the `protocolinfo` version currently supported
|
||||
// by the Tor server.
|
||||
ProtocolInfoVersion = 1
|
||||
|
||||
// MinTorVersion is the minimum supported version that the Tor server
|
||||
// must be running on. This is needed in order to create v3 onion
|
||||
// services through Tor's control port.
|
||||
MinTorVersion = "0.3.3.6"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -72,6 +78,9 @@ type Controller struct {
|
||||
// controlAddr is the host:port the Tor server is listening locally for
|
||||
// controller connections on.
|
||||
controlAddr string
|
||||
|
||||
// version is the current version of the Tor server.
|
||||
version string
|
||||
}
|
||||
|
||||
// NewController returns a new Tor controller that will be able to interact with
|
||||
@ -251,15 +260,19 @@ func (c *Controller) authenticate() error {
|
||||
}
|
||||
|
||||
// getAuthCookie retrieves the authentication cookie in bytes from the Tor
|
||||
// server. Cookie authentication must be enabled for this to work.
|
||||
// server. Cookie authentication must be enabled for this to work. The boolean
|
||||
func (c *Controller) getAuthCookie() ([]byte, error) {
|
||||
// Retrieve the authentication methods currently supported by the Tor
|
||||
// server.
|
||||
authMethods, cookieFilePath, _, err := c.ProtocolInfo()
|
||||
authMethods, cookieFilePath, version, err := c.ProtocolInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// With the version retrieved, we'll cache it now in case it needs to be
|
||||
// used later on.
|
||||
c.version = version
|
||||
|
||||
// Ensure that the Tor server supports the SAFECOOKIE authentication
|
||||
// method.
|
||||
safeCookieSupport := false
|
||||
@ -294,6 +307,47 @@ func computeHMAC256(key, message []byte) []byte {
|
||||
return mac.Sum(nil)
|
||||
}
|
||||
|
||||
// supportsV3 is a helper function that parses the current version of the Tor
|
||||
// server and determines whether it supports creationg v3 onion services through
|
||||
// Tor's control port. The version string should be of the format:
|
||||
// major.minor.revision.build
|
||||
func supportsV3(version string) error {
|
||||
// We'll split the minimum Tor version that's supported and the given
|
||||
// version in order to individually compare each number.
|
||||
requiredParts := strings.Split(MinTorVersion, ".")
|
||||
parts := strings.Split(version, ".")
|
||||
if len(parts) != 4 {
|
||||
return errors.New("version string is not of the format " +
|
||||
"major.minor.revision.build")
|
||||
}
|
||||
|
||||
// It's possible that the build number (the last part of the version
|
||||
// string) includes a pre-release string, e.g. rc, beta, etc., so we'll
|
||||
// parse that as well.
|
||||
build := strings.Split(parts[len(parts)-1], "-")
|
||||
parts[len(parts)-1] = build[0]
|
||||
|
||||
// Convert them each number from its string representation to integers
|
||||
// and check that they respect the minimum version.
|
||||
for i := range parts {
|
||||
n, err := strconv.Atoi(parts[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requiredN, err := strconv.Atoi(requiredParts[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n < requiredN {
|
||||
return fmt.Errorf("version %v below minimum version "+
|
||||
"supported %v", version, MinTorVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProtocolInfo returns the different authentication methods supported by the
|
||||
// Tor server and the version of the Tor server.
|
||||
func (c *Controller) ProtocolInfo() ([]string, string, string, error) {
|
||||
@ -338,60 +392,88 @@ func (c *Controller) ProtocolInfo() ([]string, string, string, error) {
|
||||
return authMethods, cookieFilePath, torVersion, nil
|
||||
}
|
||||
|
||||
// VirtToTargPorts is a mapping of virtual ports to target ports. When creating
|
||||
// an onion service, it will be listening externally on each virtual port. Each
|
||||
// virtual port can then be mapped to one or many target ports internally. When
|
||||
// accessing the onion service at a specific virtual port, it will forward the
|
||||
// traffic to a mapped randomly chosen target port.
|
||||
type VirtToTargPorts = map[int]map[int]struct{}
|
||||
// OnionType denotes the type of the onion service.
|
||||
type OnionType int
|
||||
|
||||
// AddOnionV2 creates a new v2 onion service and returns its onion address(es).
|
||||
// Once created, the new onion service will remain active until the connection
|
||||
// between the controller and the Tor server is closed. The path to a private
|
||||
// key can be provided in order to restore a previously created onion service.
|
||||
// If a file at this path does not exist, a new onion service will be created
|
||||
// and its private key will be saved to a file at this path. A mapping of
|
||||
// virtual ports to target ports should also be provided. Each virtual port will
|
||||
// be the ports where the onion service can be reached at, while the mapped
|
||||
// target ports will be the ports where the onion service is running locally.
|
||||
func (c *Controller) AddOnionV2(privateKeyFilename string,
|
||||
virtToTargPorts VirtToTargPorts) ([]*OnionAddr, error) {
|
||||
const (
|
||||
// V2 denotes that the onion service is V2.
|
||||
V2 OnionType = iota
|
||||
|
||||
// V3 denotes that the onion service is V3.
|
||||
V3
|
||||
)
|
||||
|
||||
// AddOnionConfig houses all of the required paramaters in order to succesfully
|
||||
// create a new onion service or restore an existing one.
|
||||
type AddOnionConfig struct {
|
||||
// Type denotes the type of the onion service that should be created.
|
||||
Type OnionType
|
||||
|
||||
// VirtualPort is the externally reachable port of the onion address.
|
||||
VirtualPort int
|
||||
|
||||
// TargetPorts is the set of ports that the service will be listening on
|
||||
// locally. The Tor server will use choose a random port from this set
|
||||
// to forward the traffic from the virtual port.
|
||||
//
|
||||
// NOTE: If nil/empty, the virtual port will be used as the only target
|
||||
// port.
|
||||
TargetPorts []int
|
||||
|
||||
// PrivateKeyPath is the full path to where the onion service's private
|
||||
// key is stored. This can be used to restore an existing onion service.
|
||||
PrivateKeyPath string
|
||||
}
|
||||
|
||||
// AddOnion creates an onion service and returns its onion address. Once
|
||||
// created, the new onion service will remain active until the connection
|
||||
// between the controller and the Tor server is closed.
|
||||
func (c *Controller) AddOnion(cfg AddOnionConfig) (*OnionAddr, error) {
|
||||
// Before sending the request to create an onion service to the Tor
|
||||
// server, we'll make sure that it supports V3 onion services if that
|
||||
// was the type requested.
|
||||
if cfg.Type == V3 {
|
||||
if err := supportsV3(c.version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// We'll start off by checking if the file containing the private key
|
||||
// exists. If it does not, then we should request the server to create
|
||||
// a new onion service and return its private key. Otherwise, we'll
|
||||
// request the server to recreate the onion server from our private key.
|
||||
var keyParam string
|
||||
if _, err := os.Stat(privateKeyFilename); os.IsNotExist(err) {
|
||||
keyParam = "NEW:RSA1024"
|
||||
if _, err := os.Stat(cfg.PrivateKeyPath); os.IsNotExist(err) {
|
||||
switch cfg.Type {
|
||||
case V2:
|
||||
keyParam = "NEW:RSA1024"
|
||||
case V3:
|
||||
keyParam = "NEW:ED25519-V3"
|
||||
}
|
||||
} else {
|
||||
privateKey, err := ioutil.ReadFile(privateKeyFilename)
|
||||
privateKey, err := ioutil.ReadFile(cfg.PrivateKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyParam = string(privateKey)
|
||||
}
|
||||
|
||||
// Now, we'll determine the different virtual ports on which this onion
|
||||
// service will be accessed by.
|
||||
// Now, we'll create a mapping from the virtual port to each target
|
||||
// port. If no target ports were specified, we'll use the virtual port
|
||||
// to provide a one-to-one mapping.
|
||||
var portParam string
|
||||
for virtPort, targPorts := range virtToTargPorts {
|
||||
// If the virtual port doesn't map to any target ports, we'll
|
||||
// use the virtual port as the target port.
|
||||
if len(targPorts) == 0 {
|
||||
portParam += fmt.Sprintf("Port=%d,%d ", virtPort,
|
||||
virtPort)
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, we'll create a mapping from the virtual port to
|
||||
// each target port.
|
||||
for targPort := range targPorts {
|
||||
portParam += fmt.Sprintf("Port=%d,%d ", virtPort,
|
||||
targPort)
|
||||
if len(cfg.TargetPorts) == 0 {
|
||||
portParam += fmt.Sprintf("Port=%d,%d ", cfg.VirtualPort,
|
||||
cfg.VirtualPort)
|
||||
} else {
|
||||
for _, targetPort := range cfg.TargetPorts {
|
||||
portParam += fmt.Sprintf("Port=%d,%d ", cfg.VirtualPort,
|
||||
targetPort)
|
||||
}
|
||||
}
|
||||
|
||||
// Send the command to create the onion service to the Tor server and
|
||||
// await its response.
|
||||
cmd := fmt.Sprintf("ADD_ONION %s %s", keyParam, portParam)
|
||||
_, reply, err := c.sendCommand(cmd)
|
||||
if err != nil {
|
||||
@ -423,7 +505,7 @@ func (c *Controller) AddOnionV2(privateKeyFilename string,
|
||||
// recreated later on.
|
||||
if privateKey, ok := replyParams["PrivateKey"]; ok {
|
||||
err := ioutil.WriteFile(
|
||||
privateKeyFilename, []byte(privateKey), 0600,
|
||||
cfg.PrivateKeyPath, []byte(privateKey), 0600,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to write private key "+
|
||||
@ -431,18 +513,11 @@ func (c *Controller) AddOnionV2(privateKeyFilename string,
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, return the different onion addresses composed of the service
|
||||
// ID, along with the onion suffix, and the different virtual ports this
|
||||
// onion service can be reached at.
|
||||
onionService := serviceID + ".onion"
|
||||
addrs := make([]*OnionAddr, 0, len(virtToTargPorts))
|
||||
for virtPort := range virtToTargPorts {
|
||||
addr := &OnionAddr{
|
||||
OnionService: onionService,
|
||||
Port: virtPort,
|
||||
}
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
// Finally, we'll return the onion address composed of the service ID,
|
||||
// along with the onion suffix, and the port this onion service can be
|
||||
// reached at externally.
|
||||
return &OnionAddr{
|
||||
OnionService: serviceID + ".onion",
|
||||
Port: cfg.VirtualPort,
|
||||
}, nil
|
||||
}
|
||||
|
68
tor/controller_test.go
Normal file
68
tor/controller_test.go
Normal file
@ -0,0 +1,68 @@
|
||||
package tor
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestParseTorVersion is a series of tests for different version strings that
|
||||
// check the correctness of determining whether they support creating v3 onion
|
||||
// services through Tor control's port.
|
||||
func TestParseTorVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
version string
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
version: "0.3.3.6",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
version: "0.3.3.7",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
version: "0.3.4.6",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
version: "0.4.3.6",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
version: "1.3.3.6",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
version: "0.3.3.6-rc",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
version: "0.3.3.7-rc",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
version: "0.3.3.5-rc",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
version: "0.3.3.5",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
version: "0.3.2.6",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
version: "0.1.3.6",
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
err := supportsV3(test.version)
|
||||
if test.valid != (err == nil) {
|
||||
t.Fatalf("test %d with version string %v failed: %v", i,
|
||||
test.version, err)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user