2016-07-01 03:18:55 +00:00
|
|
|
package core
|
|
|
|
|
2016-08-02 23:27:15 +00:00
|
|
|
import (
|
2016-08-29 00:32:59 +00:00
|
|
|
"crypto"
|
2016-08-02 23:27:15 +00:00
|
|
|
"errors"
|
2016-10-17 04:26:35 +00:00
|
|
|
"github.com/ricochet-im/ricochet-go/core/utils"
|
|
|
|
"github.com/ricochet-im/ricochet-go/rpc"
|
2016-08-02 23:27:15 +00:00
|
|
|
"github.com/yawning/bulb"
|
2016-09-27 21:24:16 +00:00
|
|
|
"golang.org/x/net/context"
|
2016-09-20 22:37:59 +00:00
|
|
|
"golang.org/x/net/proxy"
|
2016-08-02 23:27:15 +00:00
|
|
|
"log"
|
2016-08-29 00:32:59 +00:00
|
|
|
"net"
|
2016-08-28 19:21:37 +00:00
|
|
|
"strings"
|
2016-08-02 23:27:15 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
2016-07-01 03:18:55 +00:00
|
|
|
)
|
|
|
|
|
2016-09-30 05:13:55 +00:00
|
|
|
// XXX Network disconnect should kill open connections ... somehow
|
|
|
|
|
2016-07-01 03:18:55 +00:00
|
|
|
type Network struct {
|
2016-08-03 05:39:13 +00:00
|
|
|
// Connection settings; can only change while stopped
|
2016-08-02 23:27:15 +00:00
|
|
|
controlAddress string
|
|
|
|
controlPassword string
|
|
|
|
|
2016-08-03 05:39:13 +00:00
|
|
|
// Events
|
2016-08-02 23:27:15 +00:00
|
|
|
events *utils.Publisher
|
2016-08-03 05:39:13 +00:00
|
|
|
|
|
|
|
// nil when stopped, otherwise used to signal stop to active network
|
|
|
|
stopSignal chan struct{}
|
|
|
|
stoppedSignal chan struct{}
|
|
|
|
|
|
|
|
// Mutex required to access below
|
|
|
|
controlMutex sync.Mutex
|
|
|
|
|
|
|
|
// Do not use while holding controlMutex; instead, copy ptr and unlock
|
|
|
|
// mutex before use.
|
|
|
|
conn *bulb.Conn
|
|
|
|
|
|
|
|
// Modifications must be done while holding controlMutex and signalled
|
|
|
|
// to events. Do not directly modify the child elements, as they are
|
|
|
|
// pointers and may be shared. Instead, construct a new TorControlStatus
|
|
|
|
// et al for each change.
|
|
|
|
status ricochet.NetworkStatus
|
2016-08-29 00:32:59 +00:00
|
|
|
|
2016-09-20 22:37:59 +00:00
|
|
|
socksAddress socksAddress
|
|
|
|
onions []*OnionService
|
2016-08-29 00:32:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type OnionService struct {
|
2016-10-28 22:15:56 +00:00
|
|
|
Network *Network
|
2016-08-29 00:32:59 +00:00
|
|
|
OnionID string
|
|
|
|
Ports []bulb.OnionPortSpec
|
|
|
|
PrivateKey crypto.PrivateKey
|
2016-08-02 23:27:15 +00:00
|
|
|
}
|
|
|
|
|
2016-10-28 22:15:56 +00:00
|
|
|
type OnionServiceListener struct {
|
|
|
|
Service *OnionService
|
|
|
|
InternalListener net.Listener
|
|
|
|
}
|
|
|
|
|
2016-08-02 23:27:15 +00:00
|
|
|
func CreateNetwork() *Network {
|
|
|
|
return &Network{
|
|
|
|
events: utils.CreatePublisher(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-06 04:09:18 +00:00
|
|
|
func (n *Network) SetControlAddress(address string) error {
|
|
|
|
n.controlMutex.Lock()
|
|
|
|
defer n.controlMutex.Unlock()
|
|
|
|
if n.stoppedSignal != nil {
|
|
|
|
return errors.New("Network is already started")
|
|
|
|
}
|
|
|
|
|
|
|
|
n.controlAddress = address
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *Network) SetControlPassword(password string) error {
|
|
|
|
n.controlMutex.Lock()
|
|
|
|
defer n.controlMutex.Unlock()
|
|
|
|
if n.stoppedSignal != nil {
|
|
|
|
return errors.New("Network is already started")
|
|
|
|
}
|
|
|
|
|
|
|
|
n.controlPassword = password
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start connection to the tor control port. This function blocks until the first
|
|
|
|
// connection attempt is finished. The first return value says whether the
|
|
|
|
// connection has been started; if true, the connection is up even if the first
|
|
|
|
// attempt failed. The second return value is the connection attempt error, or
|
|
|
|
// nil on success.
|
|
|
|
func (n *Network) Start() (bool, error) {
|
2016-08-03 05:39:13 +00:00
|
|
|
n.controlMutex.Lock()
|
|
|
|
if n.stoppedSignal != nil {
|
|
|
|
n.controlMutex.Unlock()
|
2016-08-02 23:27:15 +00:00
|
|
|
return false, errors.New("Network is already started")
|
|
|
|
}
|
2016-11-06 04:09:18 +00:00
|
|
|
if n.controlAddress == "" {
|
|
|
|
n.controlMutex.Unlock()
|
|
|
|
return false, errors.New("Control address not configured")
|
|
|
|
}
|
2016-08-03 05:39:13 +00:00
|
|
|
n.stopSignal = make(chan struct{})
|
|
|
|
n.stoppedSignal = make(chan struct{})
|
|
|
|
n.controlMutex.Unlock()
|
2016-08-02 23:27:15 +00:00
|
|
|
|
|
|
|
connectChannel := make(chan error)
|
|
|
|
go n.run(connectChannel)
|
|
|
|
err := <-connectChannel
|
|
|
|
return true, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop the network connection. The externally-controlled tor instance
|
|
|
|
// is not affected, but the control port connection will be closed and
|
|
|
|
// the client will be offline until Start is called again. This call will
|
|
|
|
// block until the connection is stopped.
|
|
|
|
func (n *Network) Stop() {
|
2016-08-03 05:39:13 +00:00
|
|
|
// Take mutex, copy channels, nil stopSignal to avoid race if Stop()
|
|
|
|
// is called again. Other calls will still use stoppedSignal.
|
|
|
|
n.controlMutex.Lock()
|
|
|
|
stop := n.stopSignal
|
|
|
|
stopped := n.stoppedSignal
|
|
|
|
n.stopSignal = nil
|
|
|
|
n.controlMutex.Unlock()
|
2016-08-02 23:27:15 +00:00
|
|
|
|
2016-08-03 05:39:13 +00:00
|
|
|
if stop != nil {
|
|
|
|
// Signal to stop
|
|
|
|
stop <- struct{}{}
|
|
|
|
} else if stopped == nil {
|
|
|
|
// Already stopped
|
2016-08-02 23:27:15 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-03 05:39:13 +00:00
|
|
|
// Wait until stopped; safe for multiple receivers, because the channel
|
|
|
|
// is closed on stop. Sender is responsible for all other cleanup and state.
|
|
|
|
<-stopped
|
2016-08-02 23:27:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (n *Network) EventMonitor() utils.Subscribable {
|
|
|
|
return n.events
|
|
|
|
}
|
|
|
|
|
2016-08-03 05:39:13 +00:00
|
|
|
func (n *Network) GetStatus() ricochet.NetworkStatus {
|
|
|
|
n.controlMutex.Lock()
|
|
|
|
status := n.status
|
|
|
|
n.controlMutex.Unlock()
|
|
|
|
return status
|
|
|
|
}
|
|
|
|
|
2016-09-20 22:37:59 +00:00
|
|
|
type socksAddress struct {
|
|
|
|
Network string
|
|
|
|
Address string
|
|
|
|
IP net.IP
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sa socksAddress) IsValid() bool {
|
|
|
|
return sa.Network != "" && sa.Address != ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sa socksAddress) PreferredTo(other socksAddress, preferredIP net.IP) bool {
|
|
|
|
// Prefer, in order:
|
|
|
|
// - any over null
|
|
|
|
// - unix sockets over others
|
|
|
|
// - same ip as control address
|
|
|
|
// - loopback over other ips
|
|
|
|
// - first seen
|
|
|
|
|
|
|
|
if sa.Network == "" || other.Network == "" {
|
|
|
|
return other.Network == ""
|
|
|
|
}
|
|
|
|
|
|
|
|
if sa.Network == "unix" || other.Network == "unix" {
|
|
|
|
return other.Network != "unix"
|
|
|
|
}
|
|
|
|
|
|
|
|
if !preferredIP.IsUnspecified() &&
|
|
|
|
(net.IP.Equal(sa.IP, preferredIP) || net.IP.Equal(other.IP, preferredIP)) {
|
|
|
|
return !net.IP.Equal(other.IP, preferredIP)
|
|
|
|
}
|
|
|
|
|
|
|
|
if sa.IP.IsLoopback() || other.IP.IsLoopback() {
|
|
|
|
return !other.IP.IsLoopback()
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Choose the best SOCKS address out of the list in 'addresses'
|
|
|
|
// If controlAddress is non-empty, prefer a SOCKS port on the same host
|
|
|
|
func chooseSocksAddress(addresses []string, controlAddress string) (socksAddress, error) {
|
|
|
|
var selected socksAddress
|
|
|
|
var preferredIP net.IP
|
|
|
|
torOnLocalhost := true
|
|
|
|
|
|
|
|
if len(addresses) == 0 {
|
|
|
|
return selected, errors.New("No SOCKS port configured")
|
|
|
|
}
|
|
|
|
|
2016-11-06 04:09:18 +00:00
|
|
|
if !strings.HasPrefix(controlAddress, "unix:") {
|
|
|
|
addr, _, _ := net.SplitHostPort(controlAddress)
|
2016-09-20 22:37:59 +00:00
|
|
|
preferredIP = net.ParseIP(addr)
|
|
|
|
torOnLocalhost = preferredIP.IsLoopback()
|
|
|
|
}
|
|
|
|
|
|
|
|
// List of SOCKS ports, relative to the tor daemon
|
|
|
|
// Can be in the form "127.0.0.1:9050", "unix:...", or "[::1]:9050"
|
|
|
|
for _, addr := range addresses {
|
|
|
|
var socks socksAddress
|
|
|
|
|
|
|
|
// Parse into 'socks' and filter out localhost if necessary
|
|
|
|
if strings.HasPrefix(addr, "unix:") {
|
|
|
|
// Ignore unix ports for remote tor
|
|
|
|
if !torOnLocalhost {
|
|
|
|
log.Printf("Ignoring loopback SOCKS port %s", addr)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
socks = socksAddress{
|
|
|
|
Network: "unix",
|
|
|
|
Address: addr[5:],
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ipStr, _, err := net.SplitHostPort(addr)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Ignoring malformed SOCKS address '%s': %s", addr, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
socks = socksAddress{
|
|
|
|
Network: "tcp",
|
|
|
|
Address: addr,
|
|
|
|
IP: net.ParseIP(ipStr),
|
|
|
|
}
|
|
|
|
if !torOnLocalhost && socks.IP.IsLoopback() {
|
|
|
|
log.Printf("Ignoring loopback SOCKS port %s", addr)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compare to current selection
|
|
|
|
if socks.PreferredTo(selected, preferredIP) {
|
|
|
|
selected = socks
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !selected.IsValid() {
|
|
|
|
return selected, errors.New("No valid SOCKS configuration")
|
|
|
|
}
|
|
|
|
|
|
|
|
return selected, nil
|
|
|
|
}
|
|
|
|
|
2016-09-27 21:24:16 +00:00
|
|
|
func (n *Network) GetProxyDialer(forward proxy.Dialer) (proxy.Dialer, error) {
|
2016-09-20 22:37:59 +00:00
|
|
|
n.controlMutex.Lock()
|
|
|
|
socks := n.socksAddress
|
|
|
|
n.controlMutex.Unlock()
|
|
|
|
|
|
|
|
if !socks.IsValid() {
|
|
|
|
return nil, errors.New("No valid SOCKS configuration")
|
|
|
|
}
|
|
|
|
|
2016-09-27 21:24:16 +00:00
|
|
|
return proxy.SOCKS5(socks.Network, socks.Address, nil, forward)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *Network) WaitForProxyDialer(forward proxy.Dialer, c context.Context) (proxy.Dialer, error) {
|
|
|
|
var monitor <-chan interface{}
|
|
|
|
for {
|
2016-11-07 02:35:15 +00:00
|
|
|
// Check if there's a proxy address available and connection status is Ready
|
2016-09-27 21:24:16 +00:00
|
|
|
n.controlMutex.Lock()
|
|
|
|
socks := n.socksAddress
|
2016-11-07 02:35:15 +00:00
|
|
|
var connectionStatus ricochet.TorConnectionStatus
|
|
|
|
if n.status.Connection != nil {
|
|
|
|
connectionStatus = *n.status.Connection
|
|
|
|
}
|
2016-09-27 21:24:16 +00:00
|
|
|
n.controlMutex.Unlock()
|
|
|
|
|
2016-11-07 02:35:15 +00:00
|
|
|
if connectionStatus.Status == ricochet.TorConnectionStatus_READY && socks.IsValid() {
|
2016-09-27 21:24:16 +00:00
|
|
|
return proxy.SOCKS5(socks.Network, socks.Address, nil, forward)
|
|
|
|
}
|
|
|
|
|
|
|
|
if monitor == nil {
|
|
|
|
// Subscribe to connectivity change events; this is done after the first
|
|
|
|
// check to avoid overhead for the common case
|
|
|
|
monitor = n.EventMonitor().Subscribe(20)
|
|
|
|
defer n.EventMonitor().Unsubscribe(monitor)
|
|
|
|
// Check again before blocking on the monitor now that we're subscribed,
|
|
|
|
// in case the state changed since unlocking the mutex.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case _, ok := <-monitor:
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Event monitor closed")
|
|
|
|
}
|
|
|
|
case <-c.Done():
|
|
|
|
return nil, c.Err()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errors.New("No valid SOCKS configuration")
|
2016-09-20 22:37:59 +00:00
|
|
|
}
|
|
|
|
|
2016-08-29 00:32:59 +00:00
|
|
|
// Return the control connection, blocking until connected if necessary
|
|
|
|
// May return nil on failure, and the returned connection can be closed
|
|
|
|
// or otherwise fail at any time.
|
|
|
|
func (n *Network) getConnection() *bulb.Conn {
|
2016-09-02 16:51:24 +00:00
|
|
|
// Optimistically try to get a connection before subscribing to events
|
2016-08-29 00:32:59 +00:00
|
|
|
n.controlMutex.Lock()
|
|
|
|
conn := n.conn
|
|
|
|
n.controlMutex.Unlock()
|
|
|
|
if conn != nil {
|
|
|
|
return conn
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subscribe to connectivity change events
|
|
|
|
monitor := n.EventMonitor().Subscribe(20)
|
|
|
|
defer n.EventMonitor().Unsubscribe(monitor)
|
|
|
|
|
|
|
|
for {
|
|
|
|
// Check for connectivity; do this before blocking to avoid a
|
|
|
|
// race with the subscription.
|
|
|
|
n.controlMutex.Lock()
|
|
|
|
conn := n.conn
|
|
|
|
n.controlMutex.Unlock()
|
|
|
|
if conn != nil {
|
|
|
|
return conn
|
|
|
|
}
|
|
|
|
|
|
|
|
_, ok := <-monitor
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add an onion service with the provided port mappings and private key.
|
|
|
|
// If key is nil, a new RSA key is generated and returned in OnionService.
|
|
|
|
// This function will block until a control connection is available and
|
|
|
|
// the service is added or the command has failed. If the control connection
|
|
|
|
// is lost and reconnected, the service will be re-added automatically.
|
2016-09-11 04:35:03 +00:00
|
|
|
// BUG(special): Errors that occur after reconnecting cannot be detected.
|
2016-08-29 00:32:59 +00:00
|
|
|
func (n *Network) AddOnionPorts(ports []bulb.OnionPortSpec, key crypto.PrivateKey) (*OnionService, error) {
|
|
|
|
if key == nil {
|
|
|
|
// Ask for a new key, force RSA1024
|
|
|
|
key = &bulb.OnionPrivateKey{
|
|
|
|
KeyType: "NEW",
|
|
|
|
Key: "RSA1024",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
conn := n.getConnection()
|
|
|
|
info, err := conn.AddOnion(ports, key, false)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
service := &OnionService{
|
2016-10-28 22:15:56 +00:00
|
|
|
Network: n,
|
2016-08-29 00:32:59 +00:00
|
|
|
OnionID: info.OnionID,
|
|
|
|
Ports: ports,
|
|
|
|
PrivateKey: info.PrivateKey,
|
|
|
|
}
|
|
|
|
|
2016-09-16 00:16:00 +00:00
|
|
|
if service.PrivateKey == nil {
|
|
|
|
service.PrivateKey = key
|
|
|
|
}
|
|
|
|
|
2016-08-29 00:32:59 +00:00
|
|
|
n.controlMutex.Lock()
|
|
|
|
n.onions = append(n.onions, service)
|
|
|
|
n.controlMutex.Unlock()
|
|
|
|
return service, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add an onion service listening on the virtual onion port onionPort,
|
|
|
|
// with the provided private key, and return a net.Listener for it. This
|
|
|
|
// function behaves identically to AddOnionPorts, other than creating a
|
|
|
|
// listener automatically.
|
|
|
|
func (n *Network) NewOnionListener(onionPort uint16, key crypto.PrivateKey) (*OnionService, net.Listener, error) {
|
2016-10-28 22:15:56 +00:00
|
|
|
// XXX prefer unix
|
|
|
|
internalListener, err := net.Listen("tcp", "127.0.0.1:0")
|
2016-08-29 00:32:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
onionPorts := []bulb.OnionPortSpec{
|
|
|
|
bulb.OnionPortSpec{
|
|
|
|
VirtPort: onionPort,
|
2016-10-28 22:15:56 +00:00
|
|
|
Target: internalListener.Addr().String(),
|
2016-08-29 00:32:59 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
service, err := n.AddOnionPorts(onionPorts, key)
|
|
|
|
if err != nil {
|
2016-10-28 22:15:56 +00:00
|
|
|
internalListener.Close()
|
2016-08-29 00:32:59 +00:00
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2016-10-28 22:15:56 +00:00
|
|
|
listener := &OnionServiceListener{
|
|
|
|
Service: service,
|
|
|
|
InternalListener: internalListener,
|
|
|
|
}
|
|
|
|
|
2016-08-29 00:32:59 +00:00
|
|
|
return service, listener, nil
|
|
|
|
}
|
|
|
|
|
2016-10-28 22:15:56 +00:00
|
|
|
func (n *Network) DeleteOnionService(onionID string) error {
|
|
|
|
n.controlMutex.Lock()
|
|
|
|
for i, onion := range n.onions {
|
|
|
|
if onion.OnionID == onionID {
|
|
|
|
n.onions = append(n.onions[:i], n.onions[i+1:]...)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
conn := n.conn
|
|
|
|
n.controlMutex.Unlock()
|
|
|
|
|
|
|
|
if conn != nil {
|
|
|
|
return conn.DeleteOnion(onionID)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *OnionServiceListener) Accept() (net.Conn, error) {
|
|
|
|
return s.InternalListener.Accept()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *OnionServiceListener) Close() error {
|
|
|
|
s.Service.Network.DeleteOnionService(s.Service.OnionID)
|
|
|
|
return s.InternalListener.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
type OnionAddr struct {
|
|
|
|
OnionHostname string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a OnionAddr) Network() string {
|
|
|
|
return "onion"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a OnionAddr) String() string {
|
|
|
|
return a.OnionHostname
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *OnionServiceListener) Addr() net.Addr {
|
|
|
|
return OnionAddr{OnionHostname: s.Service.OnionID + ".onion"}
|
|
|
|
}
|
|
|
|
|
2016-08-02 23:27:15 +00:00
|
|
|
func (n *Network) run(connectChannel chan<- error) {
|
2016-08-03 05:39:13 +00:00
|
|
|
n.controlMutex.Lock()
|
|
|
|
stopSignal := n.stopSignal
|
|
|
|
stoppedSignal := n.stoppedSignal
|
|
|
|
n.controlMutex.Unlock()
|
2016-08-02 23:27:15 +00:00
|
|
|
|
|
|
|
for {
|
2016-08-03 05:39:13 +00:00
|
|
|
// Status to CONNECTING
|
|
|
|
n.controlMutex.Lock()
|
|
|
|
n.status.Control = &ricochet.TorControlStatus{
|
|
|
|
Status: ricochet.TorControlStatus_CONNECTING,
|
|
|
|
}
|
|
|
|
n.status.Connection = &ricochet.TorConnectionStatus{}
|
|
|
|
status := n.status
|
|
|
|
n.controlMutex.Unlock()
|
|
|
|
n.events.Publish(status)
|
|
|
|
|
|
|
|
// Attempt connection
|
2016-09-16 00:16:00 +00:00
|
|
|
errorChannel := make(chan error, 1)
|
|
|
|
err := n.connectControl()
|
|
|
|
if err != nil {
|
|
|
|
errorChannel <- err
|
2016-08-02 23:27:15 +00:00
|
|
|
} else {
|
2016-09-16 00:16:00 +00:00
|
|
|
// The goroutine polls for control events, and signals
|
|
|
|
// errorChannel on connection failure.
|
|
|
|
go n.handleControlEvents(n.conn, errorChannel)
|
|
|
|
}
|
2016-08-03 05:39:13 +00:00
|
|
|
|
2016-09-16 00:16:00 +00:00
|
|
|
// Report result of the first connection attempt
|
|
|
|
if connectChannel != nil {
|
|
|
|
connectChannel <- err
|
|
|
|
close(connectChannel)
|
|
|
|
connectChannel = nil
|
2016-08-02 23:27:15 +00:00
|
|
|
}
|
|
|
|
|
2016-09-16 00:16:00 +00:00
|
|
|
// Wait for network stop or connection errors
|
2016-08-02 23:27:15 +00:00
|
|
|
select {
|
2016-08-03 05:39:13 +00:00
|
|
|
case <-stopSignal:
|
2016-09-16 00:16:00 +00:00
|
|
|
// Close connection, clean up struct, signal status change
|
2016-08-03 05:39:13 +00:00
|
|
|
n.controlMutex.Lock()
|
2016-09-16 00:16:00 +00:00
|
|
|
if n.conn != nil {
|
|
|
|
n.conn.Close()
|
|
|
|
n.conn = nil
|
|
|
|
}
|
2016-08-03 05:39:13 +00:00
|
|
|
n.stoppedSignal = nil
|
|
|
|
n.status = ricochet.NetworkStatus{}
|
|
|
|
n.controlMutex.Unlock()
|
|
|
|
n.events.Publish(ricochet.NetworkStatus{})
|
|
|
|
|
|
|
|
// Signal stopped and exit
|
|
|
|
close(stoppedSignal)
|
|
|
|
return
|
2016-09-16 00:16:00 +00:00
|
|
|
|
|
|
|
case err := <-errorChannel:
|
2016-08-03 05:39:13 +00:00
|
|
|
if err == nil {
|
|
|
|
err = errors.New("Unknown error")
|
|
|
|
}
|
|
|
|
|
2016-09-16 00:16:00 +00:00
|
|
|
// Change status to ERROR
|
|
|
|
n.controlMutex.Lock()
|
|
|
|
if n.conn != nil {
|
|
|
|
n.conn.Close()
|
2016-08-03 05:39:13 +00:00
|
|
|
n.conn = nil
|
|
|
|
}
|
2016-09-16 00:16:00 +00:00
|
|
|
n.status.Control = &ricochet.TorControlStatus{
|
|
|
|
Status: ricochet.TorControlStatus_ERROR,
|
|
|
|
ErrorMessage: err.Error(),
|
|
|
|
}
|
|
|
|
n.status.Connection = &ricochet.TorConnectionStatus{}
|
|
|
|
status := n.status
|
|
|
|
n.controlMutex.Unlock()
|
|
|
|
n.events.Publish(status)
|
|
|
|
|
2016-08-03 05:39:13 +00:00
|
|
|
// Loop to retry connection
|
2016-09-16 00:16:00 +00:00
|
|
|
// BUG(x): This timeout is static and uninterruptable
|
|
|
|
time.Sleep(5 * time.Second)
|
2016-08-03 05:39:13 +00:00
|
|
|
}
|
2016-08-02 23:27:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-16 00:16:00 +00:00
|
|
|
func (n *Network) connectControl() error {
|
|
|
|
// Attempt connection
|
|
|
|
conn, err := createConnection(n.controlAddress, n.controlPassword)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Query ProtocolInfo for tor version
|
|
|
|
pinfo, err := conn.ProtocolInfo()
|
|
|
|
if err != nil {
|
|
|
|
conn.Close()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subscribe to events
|
|
|
|
_, err = conn.Request("SETEVENTS STATUS_CLIENT")
|
|
|
|
if err != nil {
|
|
|
|
conn.Close()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Query initial tor state
|
|
|
|
connStatus, err := queryTorState(conn)
|
|
|
|
if err != nil {
|
|
|
|
conn.Close()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-09-20 22:37:59 +00:00
|
|
|
// Choose default SOCKS port
|
|
|
|
socks, err := chooseSocksAddress(connStatus.SocksAddress, n.controlAddress)
|
|
|
|
if socks.IsValid() {
|
|
|
|
log.Printf("Discovered SOCKS port %s %s", socks.Network, socks.Address)
|
|
|
|
} else {
|
|
|
|
log.Printf("No SOCKS port: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
n.controlMutex.Lock()
|
|
|
|
|
2016-09-16 00:16:00 +00:00
|
|
|
// Copy list of onions to republish. This is done before the status
|
|
|
|
// change to avoid racing with blocked calls to AddOnionPorts, which
|
|
|
|
// will add to this list once the connection is available, but the
|
|
|
|
// publication is done afterwards.
|
|
|
|
onions := make([]*OnionService, len(n.onions))
|
|
|
|
copy(onions, n.onions)
|
|
|
|
|
|
|
|
// Update network status and set connection
|
|
|
|
n.conn = conn
|
|
|
|
n.status.Control = &ricochet.TorControlStatus{
|
|
|
|
Status: ricochet.TorControlStatus_CONNECTED,
|
|
|
|
TorVersion: pinfo.TorVersion,
|
|
|
|
}
|
|
|
|
n.status.Connection = &connStatus
|
2016-09-20 22:37:59 +00:00
|
|
|
n.socksAddress = socks
|
2016-09-16 00:16:00 +00:00
|
|
|
status := n.status
|
|
|
|
n.controlMutex.Unlock()
|
|
|
|
n.events.Publish(status)
|
|
|
|
|
|
|
|
// Re-publish onion services. Errors are not fatal to conn.
|
|
|
|
publishOnions(conn, onions)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-08-02 23:27:15 +00:00
|
|
|
func createConnection(address, password string) (*bulb.Conn, error) {
|
2016-11-06 04:09:18 +00:00
|
|
|
var net, addr string
|
|
|
|
if strings.HasPrefix(address, "unix:") {
|
|
|
|
net = "unix"
|
|
|
|
addr = address[5:]
|
|
|
|
} else {
|
|
|
|
net = "tcp"
|
|
|
|
addr = address
|
2016-08-02 23:27:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
conn, err := bulb.Dial(net, addr)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Control connection failed: %v", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = conn.Authenticate(password)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Control authentication failed: %v", err)
|
|
|
|
conn.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
conn.StartAsyncReader()
|
|
|
|
|
2016-11-06 04:09:18 +00:00
|
|
|
log.Printf("Control connection to %s successful", address)
|
2016-08-02 23:27:15 +00:00
|
|
|
return conn, nil
|
2016-07-01 03:18:55 +00:00
|
|
|
}
|
2016-08-28 19:21:37 +00:00
|
|
|
|
|
|
|
/* XXX The CIRCUIT_ESTABLISHED based connectivity logic is buggy and not
|
|
|
|
* reliable. We may not see CIRCUIT_ESTABLISHED if tor goes dormant due to
|
|
|
|
* no activity, and CIRCUIT_NOT_ESTABLISHED is _only_ sent for clock jumps,
|
|
|
|
* not any other case. For now, this is still worth using, because it at
|
|
|
|
* least gives a decent idea of when startup has finished and detects
|
|
|
|
* suspends from the clock jump.
|
|
|
|
*
|
|
|
|
* Tor also has a NETWORK_LIVENESS, but this is even less useful. In testing,
|
|
|
|
* it's entirely unable to determine when tor loses connectivity.
|
|
|
|
*
|
|
|
|
* The most reliable indicator of connectivity is probably to track active
|
|
|
|
* circs or orconns and assume connectivity if there is at least one built or
|
|
|
|
* connected. This is a little more complex, but would give us better behavior
|
|
|
|
* for figuring out when reconnection is necessary and whether we're connectable.
|
|
|
|
* If we start tracking circuits, we could also use those to gain more insight
|
|
|
|
* into the connectivity state of our services, the number of rendezvous, and
|
|
|
|
* reasons for failed outbound connections.
|
|
|
|
*/
|
|
|
|
|
2016-09-16 00:16:00 +00:00
|
|
|
func queryTorState(conn *bulb.Conn) (ricochet.TorConnectionStatus, error) {
|
|
|
|
status := ricochet.TorConnectionStatus{}
|
2016-08-28 19:21:37 +00:00
|
|
|
|
2016-09-16 00:16:00 +00:00
|
|
|
response, err := conn.Request("GETINFO status/circuit-established status/bootstrap-phase net/listeners/socks")
|
2016-08-28 19:21:37 +00:00
|
|
|
if err != nil {
|
2016-09-16 00:16:00 +00:00
|
|
|
return status, err
|
2016-08-28 19:21:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
results := make(map[string]string)
|
|
|
|
for _, rawLine := range response.Data {
|
|
|
|
line := strings.SplitN(rawLine, "=", 2)
|
|
|
|
if len(line) != 2 {
|
2016-09-16 00:16:00 +00:00
|
|
|
return status, errors.New("Invalid GETINFO response format")
|
2016-08-28 19:21:37 +00:00
|
|
|
}
|
|
|
|
results[line[0]] = strings.TrimSpace(line[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
if results["status/circuit-established"] == "0" {
|
|
|
|
if strings.Contains(results["status/bootstrap-phase"], "TAG=done") {
|
2016-09-16 00:16:00 +00:00
|
|
|
status.Status = ricochet.TorConnectionStatus_OFFLINE
|
2016-08-28 19:21:37 +00:00
|
|
|
} else {
|
2016-09-16 00:16:00 +00:00
|
|
|
status.Status = ricochet.TorConnectionStatus_BOOTSTRAPPING
|
2016-08-28 19:21:37 +00:00
|
|
|
}
|
|
|
|
} else if results["status/circuit-established"] == "1" {
|
2016-09-16 00:16:00 +00:00
|
|
|
status.Status = ricochet.TorConnectionStatus_READY
|
2016-08-28 19:21:37 +00:00
|
|
|
} else {
|
2016-09-16 00:16:00 +00:00
|
|
|
return status, errors.New("Invalid GETINFO response format")
|
2016-08-28 19:21:37 +00:00
|
|
|
}
|
|
|
|
|
2016-09-16 00:16:00 +00:00
|
|
|
status.BootstrapProgress = results["status/bootstrap-phase"]
|
|
|
|
status.SocksAddress = utils.UnquoteStringSplit(results["net/listeners/socks"], ' ')
|
|
|
|
return status, nil
|
2016-08-28 19:21:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (n *Network) handleControlEvents(conn *bulb.Conn, errorChannel chan<- error) {
|
|
|
|
for {
|
|
|
|
event, err := conn.NextEvent()
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Control connection failed: %v", err)
|
|
|
|
errorChannel <- err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.HasPrefix(event.Reply, "STATUS_CLIENT ") ||
|
|
|
|
strings.HasPrefix(event.Reply, "STATUS_GENERAL ") {
|
|
|
|
// StatusType StatusSeverity StatusAction StatusArguments
|
|
|
|
eventInfo := strings.SplitN(event.Reply, " ", 4)
|
|
|
|
if len(eventInfo) < 3 {
|
|
|
|
log.Printf("Ignoring malformed control status event")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
n.controlMutex.Lock()
|
|
|
|
changed := true
|
|
|
|
// Cannot directly modify n.status.Connection, because it may be shared; take a copy
|
|
|
|
connStatus := *n.status.Connection
|
|
|
|
|
|
|
|
if eventInfo[2] == "CIRCUIT_ESTABLISHED" {
|
|
|
|
connStatus.Status = ricochet.TorConnectionStatus_READY
|
|
|
|
} else if eventInfo[2] == "CIRCUIT_NOT_ESTABLISHED" {
|
|
|
|
if strings.Contains(connStatus.BootstrapProgress, "TAG=done") {
|
|
|
|
connStatus.Status = ricochet.TorConnectionStatus_OFFLINE
|
|
|
|
} else {
|
|
|
|
connStatus.Status = ricochet.TorConnectionStatus_BOOTSTRAPPING
|
|
|
|
}
|
|
|
|
} else if eventInfo[2] == "BOOTSTRAP" {
|
|
|
|
connStatus.BootstrapProgress = strings.Join(eventInfo[1:], " ")
|
|
|
|
} else {
|
|
|
|
changed = false
|
|
|
|
}
|
|
|
|
|
|
|
|
if changed {
|
|
|
|
n.status.Connection = &connStatus
|
|
|
|
status := n.status
|
|
|
|
n.controlMutex.Unlock()
|
|
|
|
n.events.Publish(status)
|
|
|
|
} else {
|
|
|
|
n.controlMutex.Unlock()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-29 00:32:59 +00:00
|
|
|
|
2016-09-16 00:16:00 +00:00
|
|
|
func publishOnions(conn *bulb.Conn, onions []*OnionService) {
|
2016-08-29 00:32:59 +00:00
|
|
|
for _, service := range onions {
|
|
|
|
_, err := conn.AddOnion(service.Ports, service.PrivateKey, false)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Control error for onion republication: %v", err)
|
|
|
|
}
|
|
|
|
log.Printf("Re-published onion service %s", service.OnionID)
|
|
|
|
}
|
|
|
|
}
|