core: Add functions for publishing onion services

This commit is contained in:
John Brooks 2016-08-28 18:32:59 -06:00
parent bec3784f2f
commit be5a8f1f4e
1 changed files with 125 additions and 0 deletions

View File

@ -1,12 +1,14 @@
package core package core
import ( import (
"crypto"
"errors" "errors"
"github.com/special/notricochet/core/utils" "github.com/special/notricochet/core/utils"
"github.com/special/notricochet/rpc" "github.com/special/notricochet/rpc"
"github.com/yawning/bulb" "github.com/yawning/bulb"
bulbutils "github.com/yawning/bulb/utils" bulbutils "github.com/yawning/bulb/utils"
"log" "log"
"net"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -36,6 +38,14 @@ type Network struct {
// pointers and may be shared. Instead, construct a new TorControlStatus // pointers and may be shared. Instead, construct a new TorControlStatus
// et al for each change. // et al for each change.
status ricochet.NetworkStatus status ricochet.NetworkStatus
onions []*OnionService
}
type OnionService struct {
OnionID string
Ports []bulb.OnionPortSpec
PrivateKey crypto.PrivateKey
} }
func CreateNetwork() *Network { func CreateNetwork() *Network {
@ -106,6 +116,98 @@ func (n *Network) GetStatus() ricochet.NetworkStatus {
return status return status
} }
// 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 {
// Optimistically try to get a connection before subscribin to events
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.
// BUG: Errors that occur after reconnecting cannot be detected.
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{
OnionID: info.OnionID,
Ports: ports,
PrivateKey: info.PrivateKey,
}
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) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, nil, err
}
onionPorts := []bulb.OnionPortSpec{
bulb.OnionPortSpec{
VirtPort: onionPort,
Target: listener.Addr().String(),
},
}
service, err := n.AddOnionPorts(onionPorts, key)
if err != nil {
listener.Close()
return nil, nil, err
}
return service, listener, nil
}
func (n *Network) run(connectChannel chan<- error) { func (n *Network) run(connectChannel chan<- error) {
n.controlMutex.Lock() n.controlMutex.Lock()
stopSignal := n.stopSignal stopSignal := n.stopSignal
@ -168,6 +270,9 @@ func (n *Network) run(connectChannel chan<- error) {
// signalled on connection failure. Block on retryChannel // signalled on connection failure. Block on retryChannel
// below. // below.
go n.handleControlEvents(conn, retryChannel) go n.handleControlEvents(conn, retryChannel)
// Re-publish onion services
n.publishOnions()
} }
} }
} else { } else {
@ -377,3 +482,23 @@ func (n *Network) handleControlEvents(conn *bulb.Conn, errorChannel chan<- error
} }
} }
} }
func (n *Network) publishOnions() {
n.controlMutex.Lock()
conn := n.conn
onions := make([]*OnionService, len(n.onions))
copy(onions, n.onions)
n.controlMutex.Unlock()
if conn == nil {
return
}
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)
}
}