2019-10-31 23:05:01 +00:00
|
|
|
package plugins
|
|
|
|
|
|
|
|
import (
|
|
|
|
"cwtch.im/cwtch/event"
|
2019-11-04 20:18:59 +00:00
|
|
|
"cwtch.im/cwtch/protocol/connections"
|
2020-09-28 22:07:22 +00:00
|
|
|
"fmt"
|
2020-02-10 22:09:24 +00:00
|
|
|
"git.openprivacy.ca/openprivacy/connectivity"
|
|
|
|
"git.openprivacy.ca/openprivacy/log"
|
2019-11-08 00:39:27 +00:00
|
|
|
"sync"
|
2019-10-31 23:05:01 +00:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2020-11-20 23:39:03 +00:00
|
|
|
// NetworkCheckError is a status for when the NetworkCheck Plugin has had an error making an out going connection indicating it may be offline
|
|
|
|
const NetworkCheckError = "Error"
|
|
|
|
|
|
|
|
// NetworkCheckSuccess is a status for when the NetworkCheck Plugin has had a successful message from a peer, indicating it is online right now
|
|
|
|
const NetworkCheckSuccess = "Success"
|
|
|
|
|
2019-10-31 23:05:01 +00:00
|
|
|
// networkCheck is a convenience plugin for testing high level availability of onion services
|
|
|
|
type networkCheck struct {
|
|
|
|
bus event.Manager
|
|
|
|
queue event.Queue
|
|
|
|
acn connectivity.ACN
|
2020-11-20 23:39:03 +00:00
|
|
|
onionsToCheck sync.Map // onion:string => true:bool
|
2019-10-31 23:05:01 +00:00
|
|
|
breakChan chan bool
|
|
|
|
running bool
|
2019-11-04 20:18:59 +00:00
|
|
|
offline bool
|
2019-11-08 00:39:27 +00:00
|
|
|
offlineLock sync.Mutex
|
2019-10-31 23:05:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewNetworkCheck returns a Plugin that when started will attempt various network tests
|
|
|
|
func NewNetworkCheck(bus event.Manager, acn connectivity.ACN) Plugin {
|
|
|
|
nc := &networkCheck{bus: bus, acn: acn, queue: event.NewQueue(), breakChan: make(chan bool, 1)}
|
|
|
|
return nc
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nc *networkCheck) Start() {
|
|
|
|
go nc.run()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nc *networkCheck) run() {
|
|
|
|
nc.running = true
|
2020-11-20 23:39:03 +00:00
|
|
|
nc.offline = true
|
2019-10-31 23:05:01 +00:00
|
|
|
nc.bus.Subscribe(event.ProtocolEngineStartListen, nc.queue)
|
|
|
|
nc.bus.Subscribe(event.NewMessageFromPeer, nc.queue)
|
|
|
|
nc.bus.Subscribe(event.PeerAcknowledgement, nc.queue)
|
|
|
|
nc.bus.Subscribe(event.EncryptedGroupMessage, nc.queue)
|
2019-11-04 20:18:59 +00:00
|
|
|
nc.bus.Subscribe(event.PeerStateChange, nc.queue)
|
|
|
|
nc.bus.Subscribe(event.ServerStateChange, nc.queue)
|
2020-11-20 23:39:03 +00:00
|
|
|
nc.bus.Subscribe(event.NewGetValMessageFromPeer, nc.queue)
|
|
|
|
nc.bus.Subscribe(event.NewRetValMessageFromPeer, nc.queue)
|
2019-10-31 23:05:01 +00:00
|
|
|
var lastMessageReceived time.Time
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-nc.breakChan:
|
|
|
|
nc.running = false
|
|
|
|
return
|
|
|
|
case e := <-nc.queue.OutChan():
|
|
|
|
switch e.EventType {
|
|
|
|
// On receipt of a Listen request for an onion service we will add the onion to our list
|
|
|
|
// and then we will wait a minute and check the connection for the first time (the onion should be up)
|
|
|
|
// under normal operating circumstances
|
|
|
|
case event.ProtocolEngineStartListen:
|
2021-04-13 22:12:12 +00:00
|
|
|
if _, exists := nc.onionsToCheck.Load(e.Data[event.Onion]); !exists {
|
|
|
|
log.Debugf("initiating connection check for %v", e.Data[event.Onion])
|
|
|
|
nc.onionsToCheck.Store(e.Data[event.Onion], true)
|
2021-06-02 18:34:57 +00:00
|
|
|
if time.Since(lastMessageReceived) > time.Minute {
|
2021-04-13 22:12:12 +00:00
|
|
|
nc.selfTest()
|
|
|
|
}
|
2020-11-20 23:39:03 +00:00
|
|
|
}
|
2019-11-04 20:18:59 +00:00
|
|
|
case event.PeerStateChange:
|
|
|
|
fallthrough
|
|
|
|
case event.ServerStateChange:
|
|
|
|
// if we successfully connect / authenticated to a remote server / peer then we obviously have internet
|
|
|
|
connectionState := e.Data[event.ConnectionState]
|
2019-11-08 00:39:27 +00:00
|
|
|
nc.offlineLock.Lock()
|
2020-11-20 23:39:03 +00:00
|
|
|
if connectionState == connections.ConnectionStateName[connections.AUTHENTICATED] || connectionState == connections.ConnectionStateName[connections.CONNECTED] {
|
2019-11-04 20:18:59 +00:00
|
|
|
lastMessageReceived = time.Now()
|
2020-11-20 23:39:03 +00:00
|
|
|
|
|
|
|
if nc.offline {
|
|
|
|
nc.bus.Publish(event.NewEvent(event.NetworkStatus, map[event.Field]string{event.Error: "", event.Status: NetworkCheckSuccess}))
|
|
|
|
nc.offline = false
|
|
|
|
}
|
2019-11-04 20:18:59 +00:00
|
|
|
}
|
2019-11-08 00:39:27 +00:00
|
|
|
nc.offlineLock.Unlock()
|
2019-10-31 23:05:01 +00:00
|
|
|
default:
|
|
|
|
// if we receive either an encrypted group message or a peer acknowledgement we can assume the network
|
|
|
|
// is up and running (our onion service might still not be available, but we would aim to detect that
|
|
|
|
// through other actions
|
|
|
|
// we reset out timer
|
|
|
|
lastMessageReceived = time.Now()
|
2019-11-08 00:39:27 +00:00
|
|
|
nc.offlineLock.Lock()
|
2019-11-04 20:18:59 +00:00
|
|
|
if nc.offline {
|
2020-11-20 23:39:03 +00:00
|
|
|
nc.bus.Publish(event.NewEvent(event.NetworkStatus, map[event.Field]string{event.Error: "", event.Status: NetworkCheckSuccess}))
|
2019-11-04 20:18:59 +00:00
|
|
|
nc.offline = false
|
|
|
|
}
|
2019-11-08 00:39:27 +00:00
|
|
|
nc.offlineLock.Unlock()
|
2019-10-31 23:05:01 +00:00
|
|
|
}
|
|
|
|
case <-time.After(tickTime):
|
|
|
|
// if we haven't received an action in the last minute...kick off a set of testing
|
2021-06-02 18:34:57 +00:00
|
|
|
if time.Since(lastMessageReceived) > time.Minute {
|
2020-11-20 23:39:03 +00:00
|
|
|
nc.selfTest()
|
2019-10-31 23:05:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nc *networkCheck) Shutdown() {
|
|
|
|
if nc.running {
|
|
|
|
nc.queue.Shutdown()
|
|
|
|
log.Debugf("shutting down network status plugin")
|
|
|
|
nc.breakChan <- true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-20 23:39:03 +00:00
|
|
|
func (nc *networkCheck) selfTest() {
|
|
|
|
nc.onionsToCheck.Range(func(key, val interface{}) bool {
|
|
|
|
go nc.checkConnection(key.(string))
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-10-31 23:05:01 +00:00
|
|
|
//
|
|
|
|
func (nc *networkCheck) checkConnection(onion string) {
|
2020-11-20 23:39:03 +00:00
|
|
|
prog, _ := nc.acn.GetBootstrapStatus()
|
|
|
|
if prog != 100 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-10-31 23:05:01 +00:00
|
|
|
// we want to definitively time these actions out faster than tor will, because these onions should definitely be
|
|
|
|
// online
|
2020-09-28 22:07:22 +00:00
|
|
|
ClientTimeout := TimeoutPolicy(time.Second * 60)
|
2019-10-31 23:05:01 +00:00
|
|
|
err := ClientTimeout.ExecuteAction(func() error {
|
|
|
|
conn, _, err := nc.acn.Open(onion)
|
|
|
|
if err == nil {
|
|
|
|
conn.Close()
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
})
|
2019-11-08 00:39:27 +00:00
|
|
|
nc.offlineLock.Lock()
|
|
|
|
defer nc.offlineLock.Unlock()
|
2019-10-31 23:05:01 +00:00
|
|
|
// regardless of the outcome we want to report a status to let anyone who might care know that we did do a check
|
|
|
|
if err != nil {
|
2020-11-20 23:39:03 +00:00
|
|
|
log.Debugf("publishing network error for %v -- %v\n", onion, err)
|
|
|
|
nc.bus.Publish(event.NewEvent(event.NetworkStatus, map[event.Field]string{event.Onion: onion, event.Error: err.Error(), event.Status: NetworkCheckError}))
|
|
|
|
nc.offline = true
|
2019-10-31 23:05:01 +00:00
|
|
|
} else {
|
|
|
|
log.Debugf("publishing network success for %v", onion)
|
2020-11-20 23:39:03 +00:00
|
|
|
nc.bus.Publish(event.NewEvent(event.NetworkStatus, map[event.Field]string{event.Onion: onion, event.Error: "", event.Status: NetworkCheckSuccess}))
|
|
|
|
nc.offline = false
|
2019-10-31 23:05:01 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-28 22:07:22 +00:00
|
|
|
|
|
|
|
// TODO we might want to reuse this, but for now it is only used by this plugin so it can live here
|
|
|
|
|
|
|
|
// TimeoutPolicy is an interface for enforcing common timeout patterns
|
|
|
|
type TimeoutPolicy time.Duration
|
|
|
|
|
|
|
|
// ExecuteAction runs a function and returns an error if it hasn't returned
|
|
|
|
// by the time specified by TimeoutPolicy
|
|
|
|
func (tp *TimeoutPolicy) ExecuteAction(action func() error) error {
|
|
|
|
|
|
|
|
c := make(chan error)
|
|
|
|
go func() {
|
|
|
|
c <- action()
|
|
|
|
}()
|
|
|
|
|
2021-06-02 18:34:57 +00:00
|
|
|
tick := time.NewTicker(time.Duration(*tp))
|
2020-09-28 22:07:22 +00:00
|
|
|
select {
|
2021-06-02 18:34:57 +00:00
|
|
|
case <-tick.C:
|
2020-09-28 22:07:22 +00:00
|
|
|
return fmt.Errorf("ActionTimedOutError")
|
|
|
|
case err := <-c:
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|