Error Handling

This commit is contained in:
Sarah Jamie Lewis 2019-05-21 11:28:10 -07:00
parent c769bdf208
commit b204353516
4 changed files with 73 additions and 21 deletions

View File

@ -1,5 +1,7 @@
package tapir package tapir
// Application defines the interface for all Tapir Applications
type Application interface { type Application interface {
NewInstance() Application NewInstance() Application
Init(connection *Connection) Init(connection *Connection)

View File

@ -12,18 +12,26 @@ import (
"time" "time"
) )
type AuthApp struct { // AuthMessage is exchanged between peers to obtain the Auth Capability
}
type AuthMessage struct { type AuthMessage struct {
LongTermPublicKey ed25519.PublicKey LongTermPublicKey ed25519.PublicKey
EphemeralPublicKey ed25519.PublicKey EphemeralPublicKey ed25519.PublicKey
} }
// AuthCapability defines the Authentication Capability granted by AuthApp
const AuthCapability = "AUTH"
// AuthApp is the concrete Application type that handles Authentication
type AuthApp struct {
}
// NewInstance creates a new instance of the AuthApp
func (ea AuthApp) NewInstance() Application { func (ea AuthApp) NewInstance() Application {
return new(AuthApp) return new(AuthApp)
} }
// Init runs the entire AuthApp protocol, at the end of the protocol either the connection is granted AUTH capability
// or the connection is closed.
func (ea AuthApp) Init(connection *Connection) { func (ea AuthApp) Init(connection *Connection) {
longTermPubKey := ed25519.PublicKey(connection.ID.PublicKeyBytes()) longTermPubKey := ed25519.PublicKey(connection.ID.PublicKeyBytes())
epk, esk, _ := ed25519.GenerateKey(rand.Reader) epk, esk, _ := ed25519.GenerateKey(rand.Reader)
@ -85,8 +93,9 @@ func (ea AuthApp) Init(connection *Connection) {
remoteChallenge := connection.Expect() remoteChallenge := connection.Expect()
if subtle.ConstantTimeCompare(challengeBytes, remoteChallenge) == 1 { if subtle.ConstantTimeCompare(challengeBytes, remoteChallenge) == 1 {
connection.SetHostname(utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey)) connection.SetHostname(utils.GetTorV3Hostname(remoteAuthMessage.LongTermPublicKey))
connection.SetCapability("AUTH") connection.SetCapability(AuthCapability)
} else { } else {
log.Errorf("Failed Decrypt Challenge: [%x] [%x]\n", remoteChallenge, challengeBytes) log.Errorf("Failed Decrypt Challenge: [%x] [%x]\n", remoteChallenge, challengeBytes)
connection.conn.Close()
} }
} }

View File

@ -12,7 +12,7 @@ import (
"time" "time"
) )
// Simple App is a trivial implementation of a basic p2p application // SimpleApp is a trivial implementation of a basic p2p application
type SimpleApp struct { type SimpleApp struct {
tapir.AuthApp tapir.AuthApp
} }
@ -27,7 +27,7 @@ func (ea SimpleApp) Init(connection *tapir.Connection) {
// First run the Authentication App // First run the Authentication App
ea.AuthApp.Init(connection) ea.AuthApp.Init(connection)
if connection.HasCapability("AUTH") { if connection.HasCapability(tapir.AuthCapability) {
// The code for out simple application (We just send and receive "Hello" // The code for out simple application (We just send and receive "Hello"
connection.Send([]byte("Hello")) connection.Send([]byte("Hello"))
message := connection.Expect() message := connection.Expect()
@ -53,7 +53,7 @@ func main() {
// Start a Client to Connect to the Server // Start a Client to Connect to the Server
go client(acn, pubkey) go client(acn, pubkey)
// Start the Server running the Echo App. // Start the Server running the Simple App.
var service tapir.Service var service tapir.Service
service = new(tapir.BaseOnionService) service = new(tapir.BaseOnionService)
service.Start(acn, sk, id) service.Start(acn, sk, id)
@ -69,10 +69,14 @@ func client(acn connectivity.ACN, key ed25519.PublicKey) {
var client tapir.Service var client tapir.Service
client = new(tapir.BaseOnionService) client = new(tapir.BaseOnionService)
client.Start(acn, sk, id) client.Start(acn, sk, id)
client.Connect(utils.GetTorV3Hostname(key), SimpleApp{}) cid,_ := client.Connect(utils.GetTorV3Hostname(key), SimpleApp{})
// Once connected, it shouldn't take long to authenticate and run the application. So for the purposes of this demo // Once connected, it shouldn't take long to authenticate and run the application. So for the purposes of this demo
// we will wait a little while then exit. // we will wait a little while then exit.
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)
conn,_ := client.GetConnection(cid)
log.Debugf("Client has Auth: %v", conn.HasCapability(tapir.AuthCapability))
os.Exit(0) os.Exit(0)
} }

View File

@ -15,12 +15,17 @@ import (
"sync" "sync"
) )
// Service defines the interface for a Tapir Service
type Service interface { type Service interface {
Start(acn connectivity.ACN, privateKey ed25519.PrivateKey, identity identity.Identity) Start(acn connectivity.ACN, privateKey ed25519.PrivateKey, identity identity.Identity)
Connect(hostname string, application Application) error Connect(hostname string, application Application) (string, error)
Listen(application Application) error Listen(application Application) error
GetConnection(connectionID string) (*Connection, error)
} }
// Connection defines a Tapir Connection
type Connection struct { type Connection struct {
hostname string hostname string
conn net.Conn conn net.Conn
@ -32,6 +37,7 @@ type Connection struct {
Outbound bool Outbound bool
} }
// NewConnection creates a new Connection
func NewConnection(id identity.Identity, hostname string, outbound bool, conn net.Conn, app Application) *Connection { func NewConnection(id identity.Identity, hostname string, outbound bool, conn net.Conn, app Application) *Connection {
connection := new(Connection) connection := new(Connection)
connection.hostname = hostname connection.hostname = hostname
@ -43,25 +49,33 @@ func NewConnection(id identity.Identity, hostname string, outbound bool, conn ne
return connection return connection
} }
// SetHostname sets the hostname on the connection
func (c *Connection) SetHostname(hostname string) { func (c *Connection) SetHostname(hostname string) {
log.Debugf("[%v -- %v] Asserting Remote Hostname: %v", c.ID.Hostname(), c.hostname, hostname) log.Debugf("[%v -- %v] Asserting Remote Hostname: %v", c.ID.Hostname(), c.hostname, hostname)
c.hostname = hostname c.hostname = hostname
} }
// SetCapability sets a capability on the connection
func (c *Connection) SetCapability(name string) { func (c *Connection) SetCapability(name string) {
log.Debugf("[%v -- %v] Setting Capability %v", c.ID.Hostname(), c.hostname, name) log.Debugf("[%v -- %v] Setting Capability %v", c.ID.Hostname(), c.hostname, name)
c.capabilities.Store(name, true) c.capabilities.Store(name, true)
} }
// HasCapability checks if the connection has a given capability
func (c *Connection) HasCapability(name string) bool { func (c *Connection) HasCapability(name string) bool {
_, ok := c.capabilities.Load(name) _, ok := c.capabilities.Load(name)
return ok return ok
} }
// Expect blocks and reads a single Tapir packet (1024 bytes), from the connection.
func (c *Connection) Expect() []byte { func (c *Connection) Expect() []byte {
buffer := make([]byte, 1024) buffer := make([]byte, 1024)
// TODO handle ERROR n, err := io.ReadFull(c.conn, buffer)
io.ReadFull(c.conn, buffer)
if n != 1024 || err != nil {
log.Errorf("[%v -> %v] Wire Error Reading, Read %d bytes, Error: %v", c.hostname, c.ID.Hostname(), n, err)
return []byte{}
}
//log.Debugf("[%v -> %v] Wire Receive: %x", c.hostname, c.ID.Hostname(), buffer) //log.Debugf("[%v -> %v] Wire Receive: %x", c.hostname, c.ID.Hostname(), buffer)
if c.encrypted { if c.encrypted {
@ -79,11 +93,14 @@ func (c *Connection) Expect() []byte {
return buffer[2 : len+2] return buffer[2 : len+2]
} }
// SetEncryptionKey turns on application-level encryption on the connection using the given key.
func (c *Connection) SetEncryptionKey(key [32]byte) { func (c *Connection) SetEncryptionKey(key [32]byte) {
c.key = key c.key = key
c.encrypted = true c.encrypted = true
} }
// Send writes a given message to a Tapir packet (of 1024 bytes in length).
func (c *Connection) Send(message []byte) { func (c *Connection) Send(message []byte) {
maxLength := 1024 maxLength := 1024
@ -104,6 +121,7 @@ func (c *Connection) Send(message []byte) {
c.conn.Write(buffer) c.conn.Write(buffer)
} }
// BaseOnionService is a concrete implementation of the service interface over Tor onion services.
type BaseOnionService struct { type BaseOnionService struct {
connections sync.Map connections sync.Map
acn connectivity.ACN acn connectivity.ACN
@ -111,6 +129,9 @@ type BaseOnionService struct {
privateKey ed25519.PrivateKey privateKey ed25519.PrivateKey
} }
// Start initializes a BaseOnionService with a given private key and identity
// The private key is needed to initialize the Onion listen socket, ideally we could just pass an Identity in here.
func (s *BaseOnionService) Start(acn connectivity.ACN, sk ed25519.PrivateKey, id identity.Identity) { func (s *BaseOnionService) Start(acn connectivity.ACN, sk ed25519.PrivateKey, id identity.Identity) {
// run add onion // run add onion
// get listen context // get listen context
@ -119,24 +140,42 @@ func (s *BaseOnionService) Start(acn connectivity.ACN, sk ed25519.PrivateKey, id
s.privateKey = sk s.privateKey = sk
} }
func (s *BaseOnionService) GetConnection(hostname string) error {
return errors.New("no connection found") // GetConnection returns a connection for a given hostname.
func (s *BaseOnionService) GetConnection(connectionID string) (*Connection, error) {
conn,ok := s.connections.Load(connectionID)
if !ok {
return nil, errors.New("no connection found")
}
connection := conn.(*Connection)
return connection, nil
} }
func (s *BaseOnionService) Connect(hostname string, app Application) error { // Connect initializes a new outbound connection to the given peer, using the defined Application
func (s *BaseOnionService) Connect(hostname string, app Application) (string, error) {
// connects to a remote server // connects to a remote server
// spins off to a connection struct // spins off to a connection struct
log.Debugf("Connecting to %v", hostname) log.Debugf("Connecting to %v", hostname)
conn, _, err := s.acn.Open(hostname) conn, _, err := s.acn.Open(hostname)
if err == nil { if err == nil {
log.Debugf("Connected to %v", hostname) connectionID := s.getNewConnectionID()
s.connections.Store(hostname, NewConnection(s.id, hostname, true, conn, app.NewInstance())) log.Debugf("Connected to %v [%v]", hostname, connectionID)
return nil s.connections.Store(connectionID, NewConnection(s.id, hostname, true, conn, app.NewInstance()))
return connectionID, nil
} }
log.Debugf("Error connecting to %v %v", hostname, err) log.Debugf("Error connecting to %v %v", hostname, err)
return err return "", err
} }
func (s *BaseOnionService) getNewConnectionID() string {
id := make([]byte, 10)
rand.Read(id)
connectionID := "connection-" + base64.StdEncoding.EncodeToString(id)
return connectionID
}
// Listen starts a blocking routine that waits for incoming connections and initializes connections with them based
// on the given Application.
func (s *BaseOnionService) Listen(app Application) error { func (s *BaseOnionService) Listen(app Application) error {
// accepts a new connection // accepts a new connection
// spins off to a connection struct // spins off to a connection struct
@ -146,9 +185,7 @@ func (s *BaseOnionService) Listen(app Application) error {
for { for {
conn, err := ls.Accept() conn, err := ls.Accept()
if err == nil { if err == nil {
id := make([]byte, 10) tempHostname := s.getNewConnectionID()
rand.Read(id)
tempHostname := "unknown-" + base64.StdEncoding.EncodeToString(id)
log.Debugf("Accepted connection from %v", tempHostname) log.Debugf("Accepted connection from %v", tempHostname)
s.connections.Store(tempHostname, NewConnection(s.id, tempHostname, false, conn, app.NewInstance())) s.connections.Store(tempHostname, NewConnection(s.id, tempHostname, false, conn, app.NewInstance()))
} else { } else {