core: Prototype tor control connection and API
This commit is contained in:
parent
74dc289e09
commit
7733c6d572
172
core/network.go
172
core/network.go
|
@ -1,15 +1,169 @@
|
|||
package core
|
||||
|
||||
type NetworkStatus int
|
||||
|
||||
const (
|
||||
NetworkUnavailable NetworkStatus = iota
|
||||
NetworkError
|
||||
NetworkOffline
|
||||
NetworkOnline
|
||||
import (
|
||||
"errors"
|
||||
"github.com/special/ricochet-go/core/utils"
|
||||
"github.com/yawning/bulb"
|
||||
bulbutils "github.com/yawning/bulb/utils"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Network struct {
|
||||
torConfig *TorConfiguration
|
||||
status NetworkStatus
|
||||
conn *bulb.Conn
|
||||
|
||||
controlAddress string
|
||||
controlPassword string
|
||||
|
||||
handleLock sync.Mutex
|
||||
stopChannel chan struct{}
|
||||
|
||||
events *utils.Publisher
|
||||
}
|
||||
|
||||
func CreateNetwork() *Network {
|
||||
return &Network{
|
||||
events: utils.CreatePublisher(),
|
||||
}
|
||||
}
|
||||
|
||||
// Start connection to the tor control port at 'address', with the optional
|
||||
// control password 'password'. 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(address, password string) (bool, error) {
|
||||
n.handleLock.Lock()
|
||||
defer n.handleLock.Unlock()
|
||||
|
||||
if n.stopChannel != nil {
|
||||
// This is an error, because address/password might not be the same
|
||||
return false, errors.New("Network is already started")
|
||||
}
|
||||
|
||||
n.stopChannel = make(chan struct{})
|
||||
n.controlAddress = address
|
||||
n.controlPassword = password
|
||||
|
||||
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() {
|
||||
n.handleLock.Lock()
|
||||
defer n.handleLock.Unlock()
|
||||
|
||||
if n.stopChannel == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// XXX but if we do block that long, we can hold the mutex a _long_ time.
|
||||
// XXX so the mutex won't be suitable for a "is network started" check
|
||||
n.stopChannel <- struct{}{}
|
||||
n.stopChannel = nil
|
||||
n.conn = nil
|
||||
n.controlAddress = ""
|
||||
n.controlPassword = ""
|
||||
}
|
||||
|
||||
func (n *Network) EventMonitor() utils.Subscribable {
|
||||
return n.events
|
||||
}
|
||||
|
||||
func (n *Network) run(connectChannel chan<- error) {
|
||||
var conn *bulb.Conn
|
||||
var err error
|
||||
|
||||
for {
|
||||
conn, err = createConnection(n.controlAddress, n.controlPassword)
|
||||
|
||||
// Report result of the first connection attempt
|
||||
if connectChannel != nil {
|
||||
connectChannel <- err
|
||||
close(connectChannel)
|
||||
connectChannel = nil
|
||||
}
|
||||
|
||||
retryChannel := make(chan struct{}, 1)
|
||||
|
||||
if err == nil {
|
||||
// Success! Set connection, handle events, and retry connection
|
||||
// if it fails
|
||||
n.conn = conn
|
||||
n.events.Publish(true)
|
||||
go func() {
|
||||
for {
|
||||
event, err := conn.NextEvent()
|
||||
if err != nil {
|
||||
log.Printf("Control connection failed: %v", err)
|
||||
n.events.Publish(false)
|
||||
retryChannel <- struct{}{}
|
||||
break
|
||||
}
|
||||
log.Printf("Control event: %v", event)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
// Failed; retry in one second
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
retryChannel <- struct{}{}
|
||||
}()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-n.stopChannel:
|
||||
break
|
||||
case <-retryChannel:
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stopped
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func createConnection(address, password string) (*bulb.Conn, error) {
|
||||
net, addr, err := bulbutils.ParseControlPortString(address)
|
||||
if err != nil {
|
||||
log.Printf("Parsing control network address '%s' failed: %v", address, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
// XXX
|
||||
if _, err := conn.Request("SETEVENTS STATUS_CLIENT"); err != nil {
|
||||
log.Printf("Control events failed: %v", err)
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Print("Control connected!")
|
||||
return conn, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue