-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
multi: add onion services support #1159
multi: add onion services support #1159
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work! The addition of onion services will be a major boon to routing node operation due to the increased privacy, and also on the client end w.r.t completely handling NAT traversal.
I've yet to test this out locally, so this is an initial review pass. One thing I pointed out is that we'll need to ensure that we properly handle re-connecting back to inbound connections as it'll show the address of the proxy rather than the hidden service address.
tor/README.md
Outdated
The tor package contains utility functions that allow for interacting with the | ||
Tor daemon. So far, supported functions include routing all traffic over Tor's | ||
exposed socks5 proxy and routing DNS queries over Tor (A, AAAA, SRV). In the | ||
future more features will be added: automatic setup of v2 hidden service |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, this PR implements most of these features. Still going through the set of commits, so perhaps it's reconciled later in the branch history.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
tor/net.go
Outdated
if network != "tcp" { | ||
return nil, fmt.Errorf("cannot dial non-tcp network via Tor") | ||
} | ||
return Dial(address, p.SOCKSPort, p.StreamIsolation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do all these messages need to be public? Given that most callers will interact directly with the this ProxyNet
struct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On a second glance, I guess they're nice as it lets callers use specific functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's either we make them public or we have a constructor for ProxyNet
specifying them, so it's pretty much the same thing. If we happen to add some private vars to ProxyNet
then we can update it to use a constructor instead.
tor/tor.go
Outdated
) | ||
|
||
const ( | ||
localhost = "127.0.0.1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we also need to consider IPv6 localhost, or nah?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed this so that callers can specify the host:port of the SOCKS proxy, rather than just the port. Within lnd
, the default is still set to IPv4 localhost.
// Tor will create a new circuit for each set of credentials. | ||
var auth *proxy.Auth | ||
if streamIsolation { | ||
var b [16]byte |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, this gives us one less dependnacy!
(no need for btcsuite/go-socks
anymore)
tor/tor.go
Outdated
|
||
// Once connected, we'll construct the SRV request for the host | ||
// following the format _service._proto.name. as described in RFC #2782. | ||
host := "_" + service + "._" + proto + "." + name + "." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll need to double check to ensure that this is compliant with the new DNS scheme, take not of this commit which recently modified the way we construct this query for regular UDP:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. We could call LookupSRV("nodes", "tcp", "nodes.lightning.directory")
and that would translate to _nodes._tcp.nodes.lightning.directory.
, which follows the changes in the commits above.
tor/controller.go
Outdated
return nil, errors.New("private key not found in reply") | ||
} | ||
|
||
err := ioutil.WriteFile(privateKeyFile, []byte(privateKey), 0600) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the comment above, perhaps we should encode this in the proper format, so we don't need to do any special decoding on start up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment above.
@@ -604,6 +657,10 @@ func (s *server) Stop() error { | |||
|
|||
close(s.quit) | |||
|
|||
if s.torController != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't see where the controller was even started in the first place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has now been moved to server.Start()
.
config.go
Outdated
defaultTorDNS = "soa.nodes.lightning.directory:53" | ||
defaultTorSOCKSPort = 9050 | ||
defaultTorControlPort = 9051 | ||
defaultTorPrivateKeyFilename = "onion_private_key" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May want to prefix this with v2
(the variable name), and also the file name since a future version might let the user specify their own private key when we extend to also support auto v3
over the control port.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
config.go
Outdated
ControlPort int `long:"controlport" description:"The port that Tor is listening on for Tor control connections -- NOTE port must be between 1024 and 65535"` | ||
V2 bool `long:"v2" description:"Automatically set up a v2 onion service to listen for inbound connections"` | ||
V3 bool `long:"v3" description:"Use 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"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar comment here w.r.t to v2
prefix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
config.go
Outdated
@@ -84,6 +86,8 @@ var ( | |||
|
|||
defaultBitcoindDir = btcutil.AppDataDir("bitcoin", false) | |||
defaultLitecoindDir = btcutil.AppDataDir("litecoin", false) | |||
|
|||
defaultTorPrivateKeyPath = filepath.Join(defaultLndDir, defaultTorPrivateKeyFilename) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar comment here w.r.t to v2
prefix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
a5fa007
to
01161a1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have much to pick on for this PR! Mostly LGTM, and I have also tested this together with @wpaulino, working as advertised!
I dig how easy it is to set up lnd
for Tor, new world order where lnd
is over Tor by default incoming 😈
|
||
// Dial uses the Tor Dial function in order to establish connections through | ||
// Tor. Since Tor only supports TCP connections, only TCP networks are allowed. | ||
func (p *ProxyNet) Dial(network, address string) (net.Conn, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not move this to tor.go
, and just put the connection logic in here (instead of making these wrap the calls to Dial(..)
etc)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My guess is in order to provide calls like the net
package does without the need of a ProxyNet
struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still don't see why you would need that, if the calls are only done only for ProxyNet
anyway.
tor/controller.go
Outdated
// Once retrieved, we'll ensure these values are of proper length when | ||
// decoded. | ||
serverHash, exists := replyParams["SERVERHASH"] | ||
if !exists { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style nit: usually ok
is used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
tor/controller.go
Outdated
} | ||
decodedServerHash, err := hex.DecodeString(serverHash) | ||
if err != nil { | ||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should append to the error which string decoding is failing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
tor/controller.go
Outdated
} | ||
decodedServerNonce, err := hex.DecodeString(serverNonce) | ||
if err != nil { | ||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
func computeHMAC256(key, message []byte) []byte { | ||
mac := hmac.New(sha256.New, key) | ||
mac.Write(message) | ||
return mac.Sum(nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would the last to lines be equivalent to just doing return mac.Sum(message)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sum
prepends the message
to the current hash. w/o any calls to Write
, it would return message || H_K(nil)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
config.go
Outdated
Control: defaultTorControl, | ||
// TODO: change this to v3 once we're able to | ||
// automatically set them up. | ||
V2: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if it's obvious that this will be true
by default. Looks like the default is not shown in lnd -h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in testing, i found that this causes a collision when trying to run v3, i get this error:
either tor.v2 or tor.v3 can be set, but not both
I had to set tor.v2=0
and tor.v3=1
to get around it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
server.go
Outdated
// future, this will be expanded to do so. | ||
if cfg.Tor.Active && cfg.Tor.V2 { | ||
s.torController = tor.NewController(cfg.Tor.Control) | ||
if err := s.torController.Start(); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this can be moved to the server's Start
method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
docs/configuring_tor.md
Outdated
|
||
## 2. Outbound Connections Only | ||
protocol traffic_ is tunneled over Tor. Users must ensure that when also running | ||
a backend node, that it is also proxying all traffic over Tor. If using the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should keep the "Bitcoin full-node" jargon here, as "backend node" might not be known to as many.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! I've tested both v2 and v3 and both work :)
tor/net.go
Outdated
|
||
// RegularNet is an implementation of the Net interface that defines behaviour | ||
// for regular network connections. | ||
type RegularNet struct{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
naming suggestion: ClearNet
? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
func computeHMAC256(key, message []byte) []byte { | ||
mac := hmac.New(sha256.New, key) | ||
mac.Write(message) | ||
return mac.Sum(nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tor/net.go
Outdated
// ResolveTCPAddr uses the Tor ResolveTCPAddr function in order to resolve TCP | ||
// addresses over Tor. | ||
func (p *ProxyNet) ResolveTCPAddr(network, address string) (*net.TCPAddr, error) { | ||
if network != "tcp" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this check include "tcp4"
and "tcp6"
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep! Fixed.
tor/tor.go
Outdated
|
||
// Once connected, we'll construct the SRV request for the host | ||
// following the format _service._proto.name. as described in RFC #2782. | ||
host := "_" + service + "._" + proto + "." + name + "." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would string formatting be clearer here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
config.go
Outdated
Control: defaultTorControl, | ||
// TODO: change this to v3 once we're able to | ||
// automatically set them up. | ||
V2: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in testing, i found that this causes a collision when trying to run v3, i get this error:
either tor.v2 or tor.v3 can be set, but not both
I had to set tor.v2=0
and tor.v3=1
to get around it
docs/configuring_tor.md
Outdated
``` | ||
|
||
Once v3 onion service support is stable, `lnd` will be updated to also | ||
automatically set up v3 onion services. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These docs are great! :) I might suggest adding a sample torrc for v3 nodes as well, it may not be immediately obvious for everyone wrt. what needs to change from the sample v2 torrc given above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docs added :)
--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.streamisolation Enable Tor stream isolation by randomizing user credentials for each connection. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does everyone think of defaulting to using streamisolation, and instead have a no-streamisolation
option?
8095722
to
4333c49
Compare
bdcbf49
to
bb5cc04
Compare
Needs a rebase to address conflict after the latest merge. |
bb5cc04
to
c02efa4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🕺🏾
Just clarifying, current default is still no-streamisolation correct? |
Yep, it has performance implications as you'll incur the overhead of a new circuit for each stream. |
Also there're diff forms of isolation: per user, per connection, etc. We can revisit this in the future though, no reason to block this PR from inclusion. |
config.go
Outdated
cfg.Tor.Control, strconv.Itoa(defaultTorControlPort), | ||
) | ||
switch { | ||
case !cfg.Tor.V2 && !cfg.Tor.V3: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point, if either of them are active, we should ensure that lnd
is only listening on local host.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO it shouldn't be assumed that if the user wants to use tor for outbound connections, then they also want to use it for inbound connections. So if v2 and v3 aren't set, then we should also disable listening for the p2p interface all together.
server.go
Outdated
@@ -860,6 +913,46 @@ func (s *server) peerBootstrapper(numTargetPeers uint32, | |||
} | |||
} | |||
|
|||
func (s *server) initTorController() error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing godoc
comment.
config.go
Outdated
cfg.Tor.Control, strconv.Itoa(defaultTorControlPort), | ||
) | ||
switch { | ||
case !cfg.Tor.V2 && !cfg.Tor.V3: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO it shouldn't be assumed that if the user wants to use tor for outbound connections, then they also want to use it for inbound connections. So if v2 and v3 aren't set, then we should also disable listening for the p2p interface all together.
config.go
Outdated
case cfg.Tor.V2 && cfg.Tor.V3: | ||
return nil, errors.New("either tor.v2 or tor.v3 can be set, " + | ||
"but not both") | ||
case cfg.DisableListen && (cfg.Tor.V2 || cfg.Tor.V3): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be a check right below this to disable listening (automatically) if tor is set, but v2 and v3 aren't.
config.go
Outdated
@@ -434,6 +434,12 @@ func loadConfig() (*config, error) { | |||
case cfg.DisableListen && (cfg.Tor.V2 || cfg.Tor.V3): | |||
return nil, errors.New("listening must be enabled when " + | |||
"enabling inbound connections over Tor") | |||
case !cfg.Tor.V2 || !cfg.Tor.V3: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be more like:
case cfg.Tor && (!cfg.Tor.V2 || !cfg.Tor.V3):
Otherwise would always do this.
In this commit, we clean up the tor package to better follow the Effective Go guidelines. Most of the changes revolve around naming, where we'd have things like `torsvc.TorDial`. This was simplified to `tor.Dial` along with many others.
Co-Authored-By: Eugene <crypt-iq@users.noreply.github.com>
Co-Authored-By: Eugene <crypt-iq@users.noreply.github.com>
Co-Authored-By: Eugene <crypt-iq@users.noreply.github.com>
In this commit, we update the set of Tor flags to use sane defaults when not specified. We also include some new flags related to the recent onion services changes. This allows users to easily get set up on Tor by only specifying the tor.active flag. If needed, the defaults can still be overridden.
In this commit, we allow `lnd` to properly parse onion addresses in order to advertise them to the network when set through the `--externalip` flag. Co-Authored-By: Eugene <crypt-iq@users.noreply.github.com>
In this commit, we now allow connections to onion addresses due to recently adding support to properly parse them. Co-Authored-By: Eugene <crypt-iq@users.noreply.github.com>
In this commit, we fix a bug where a fallback SRV lookup would leak information if `lnd` was set to route connections over Tor. We solve this by using the network-specific functions rather than the standard ones found in the `net` package.
In this commit, we allow the daemon to use the recently introduced Tor Controller implementation. This will automatically create a v2 onion service at startup in order to listen for inbound connections over Tor. Co-Authored-By: Eugene <crypt-iq@users.noreply.github.com>
In this commit, we update the way we reestablish inbound connections if we lose connectivity to a node we have an open channel with. Rather than fetching the node's advertised port, we'll fetch one of their advertised addresses instead. This ensure that if the remote node is running behind a proxy, we do not see the proxy's address.
In this commit, we go through the codebase looking for TCP address assumptions and modifying them to include the recently introduced onion addresses. This enables us to fully support onion addresses within the daemon.
8481519
to
2e0484b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM ☄️
regarding lnd/pull/1159/commits/2e0484be19e1a146895489b1f25cdfce8b23f589 - now we can set our domain names and subdomains as the target? thank you @wpaulino |
This PR continues the work from #796. Huge thanks to @Crypt-iQ for getting this started! I decided to create my own branch rather than rebasing theirs, but credit has been given to them in the relevant commits.
This PR introduces the following changes:
channeldb
andlnwire
.Fixes #186.