2017-05-02 23:33:51 +00:00
|
|
|
package connection
|
|
|
|
|
|
|
|
import (
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
"context"
|
2017-05-02 23:33:51 +00:00
|
|
|
"errors"
|
2017-07-04 18:29:11 +00:00
|
|
|
"fmt"
|
2017-05-02 23:33:51 +00:00
|
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
"github.com/s-rah/go-ricochet/channels"
|
|
|
|
"github.com/s-rah/go-ricochet/utils"
|
|
|
|
"github.com/s-rah/go-ricochet/wire/control"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Connection encapsulates the state required to maintain a connection to
|
|
|
|
// a ricochet service.
|
|
|
|
type Connection struct {
|
|
|
|
utils.RicochetNetwork
|
|
|
|
|
|
|
|
channelManager *ChannelManager
|
|
|
|
|
|
|
|
// Ricochet Network Loop
|
|
|
|
packetChannel chan utils.RicochetData
|
|
|
|
errorChannel chan error
|
|
|
|
|
|
|
|
breakChannel chan bool
|
|
|
|
breakResultChannel chan bool
|
|
|
|
|
|
|
|
unlockChannel chan bool
|
|
|
|
unlockResponseChannel chan bool
|
|
|
|
|
|
|
|
messageBuilder utils.MessageBuilder
|
2017-07-04 18:29:11 +00:00
|
|
|
trace bool
|
2017-05-02 23:33:51 +00:00
|
|
|
|
|
|
|
Conn io.ReadWriteCloser
|
|
|
|
IsInbound bool
|
|
|
|
Authentication map[string]bool
|
|
|
|
RemoteHostname string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rc *Connection) init() {
|
|
|
|
|
|
|
|
rc.packetChannel = make(chan utils.RicochetData)
|
|
|
|
rc.errorChannel = make(chan error)
|
|
|
|
|
|
|
|
rc.breakChannel = make(chan bool)
|
|
|
|
rc.breakResultChannel = make(chan bool)
|
|
|
|
|
|
|
|
rc.unlockChannel = make(chan bool)
|
|
|
|
rc.unlockResponseChannel = make(chan bool)
|
|
|
|
|
|
|
|
rc.Authentication = make(map[string]bool)
|
|
|
|
go rc.start()
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewInboundConnection creates a new Connection struct
|
|
|
|
// modelling an Inbound Connection
|
|
|
|
func NewInboundConnection(conn io.ReadWriteCloser) *Connection {
|
|
|
|
rc := new(Connection)
|
|
|
|
rc.Conn = conn
|
|
|
|
rc.IsInbound = true
|
|
|
|
rc.init()
|
|
|
|
rc.channelManager = NewServerChannelManager()
|
|
|
|
return rc
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewOutboundConnection creates a new Connection struct
|
|
|
|
// modelling an Inbound Connection
|
|
|
|
func NewOutboundConnection(conn io.ReadWriteCloser, remoteHostname string) *Connection {
|
|
|
|
rc := new(Connection)
|
|
|
|
rc.Conn = conn
|
|
|
|
rc.IsInbound = false
|
|
|
|
rc.init()
|
|
|
|
rc.RemoteHostname = remoteHostname
|
|
|
|
rc.channelManager = NewClientChannelManager()
|
|
|
|
return rc
|
|
|
|
}
|
|
|
|
|
2017-06-27 19:24:04 +00:00
|
|
|
func (rc *Connection) TraceLog(enabled bool) {
|
2017-07-04 18:29:11 +00:00
|
|
|
rc.trace = enabled
|
2017-06-27 19:24:04 +00:00
|
|
|
}
|
|
|
|
|
2017-05-02 23:33:51 +00:00
|
|
|
// start
|
|
|
|
func (rc *Connection) start() {
|
|
|
|
for {
|
|
|
|
packet, err := rc.RecvRicochetPacket(rc.Conn)
|
|
|
|
if err != nil {
|
|
|
|
rc.errorChannel <- err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
rc.packetChannel <- packet
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
// Do allows any function utilizing Connection to be run safely, if you're
|
|
|
|
// careful. All operations which require access (directly or indirectly) to
|
|
|
|
// Connection while Process is running need to use Do. Calls to Do without
|
|
|
|
// Process running will block.
|
|
|
|
//
|
|
|
|
// Like a mutex, Do cannot be called recursively. This will deadlock. As
|
|
|
|
// a result, no API in this library that can be reached from the application
|
|
|
|
// should use Do, with few exceptions. This would make the API impossible
|
|
|
|
// to use safely in many cases.
|
|
|
|
//
|
|
|
|
// Do is safe to call from methods of connection.Handler and channel.Handler
|
|
|
|
// that are called by Process.
|
2017-05-02 23:33:51 +00:00
|
|
|
func (rc *Connection) Do(do func() error) error {
|
|
|
|
// Force process to soft-break so we can lock
|
2017-06-27 19:24:04 +00:00
|
|
|
rc.traceLog("request unlocking of process loop for do()")
|
2017-05-02 23:33:51 +00:00
|
|
|
rc.unlockChannel <- true
|
2017-06-27 19:24:04 +00:00
|
|
|
rc.traceLog("process loop is unlocked for do()")
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
defer func() {
|
|
|
|
rc.traceLog("giving up lock process loop after do() ")
|
|
|
|
rc.unlockResponseChannel <- true
|
|
|
|
}()
|
|
|
|
return do()
|
|
|
|
}
|
|
|
|
|
|
|
|
// DoContext behaves in the same way as Do, but also respects the provided
|
|
|
|
// context when blocked, and passes the context to the callback function.
|
|
|
|
//
|
|
|
|
// DoContext should be used when any call to Do may need to be cancelled
|
|
|
|
// or timed out.
|
|
|
|
func (rc *Connection) DoContext(ctx context.Context, do func(context.Context) error) error {
|
|
|
|
// Force process to soft-break so we can lock
|
|
|
|
rc.traceLog("request unlocking of process loop for do()")
|
|
|
|
select {
|
|
|
|
case rc.unlockChannel <- true:
|
|
|
|
break
|
|
|
|
case <-ctx.Done():
|
|
|
|
rc.traceLog("giving up on unlocking process loop for do() because context cancelled")
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
|
|
|
|
rc.traceLog("process loop is unlocked for do()")
|
|
|
|
defer func() {
|
|
|
|
rc.traceLog("giving up lock process loop after do() ")
|
|
|
|
rc.unlockResponseChannel <- true
|
|
|
|
}()
|
|
|
|
|
|
|
|
return do(ctx)
|
2017-05-02 23:33:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// RequestOpenChannel sends an OpenChannel message to the remote client.
|
2017-09-16 13:46:28 +00:00
|
|
|
// An error is returned only if the requirements for opening this channel
|
|
|
|
// are not met on the local side (a nil error return does not mean the
|
|
|
|
// channel was opened successfully, because channels open asynchronously).
|
2017-09-17 21:01:22 +00:00
|
|
|
func (rc *Connection) RequestOpenChannel(ctype string, handler channels.Handler) (*channels.Channel, error) {
|
2017-07-04 18:29:11 +00:00
|
|
|
rc.traceLog(fmt.Sprintf("requesting open channel of type %s", ctype))
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
|
|
|
|
// Check that we have the authentication already
|
|
|
|
if handler.RequiresAuthentication() != "none" {
|
|
|
|
// Enforce Authentication Check.
|
|
|
|
_, authed := rc.Authentication[handler.RequiresAuthentication()]
|
|
|
|
if !authed {
|
|
|
|
return nil, utils.UnauthorizedActionError
|
2017-05-02 23:33:51 +00:00
|
|
|
}
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
}
|
2017-05-02 23:33:51 +00:00
|
|
|
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
channel, err := rc.channelManager.OpenChannelRequest(handler)
|
2017-05-02 23:33:51 +00:00
|
|
|
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
if err != nil {
|
|
|
|
rc.traceLog(fmt.Sprintf("failed to request open channel of type %v", err))
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-05-02 23:33:51 +00:00
|
|
|
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
channel.SendMessage = func(message []byte) {
|
|
|
|
rc.SendRicochetPacket(rc.Conn, channel.ID, message)
|
|
|
|
}
|
|
|
|
channel.DelegateAuthorization = func() {
|
|
|
|
rc.Authentication[handler.Type()] = true
|
|
|
|
}
|
|
|
|
channel.CloseChannel = func() {
|
|
|
|
rc.SendRicochetPacket(rc.Conn, channel.ID, []byte{})
|
|
|
|
rc.channelManager.RemoveChannel(channel.ID)
|
|
|
|
}
|
|
|
|
response, err := handler.OpenOutbound(channel)
|
|
|
|
if err == nil {
|
|
|
|
rc.traceLog(fmt.Sprintf("requested open channel of type %s", ctype))
|
|
|
|
rc.SendRicochetPacket(rc.Conn, 0, response)
|
|
|
|
} else {
|
|
|
|
rc.traceLog(fmt.Sprintf("failed to request open channel of type %v", err))
|
|
|
|
rc.channelManager.RemoveChannel(channel.ID)
|
|
|
|
}
|
|
|
|
return channel, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// processUserCallback should be used to wrap any calls into handlers or
|
|
|
|
// application code from the Process goroutine. It handles calls to Do
|
|
|
|
// from within that code to prevent deadlocks.
|
|
|
|
func (rc *Connection) processUserCallback(cb func()) {
|
|
|
|
done := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
defer close(done)
|
|
|
|
cb()
|
|
|
|
}()
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-done:
|
|
|
|
return
|
|
|
|
case <-rc.unlockChannel:
|
|
|
|
<-rc.unlockResponseChannel
|
2017-05-02 23:33:51 +00:00
|
|
|
}
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
}
|
2017-05-02 23:33:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Process receives socket and protocol events for the connection. Methods
|
|
|
|
// of the application-provided `handler` will be called from this goroutine
|
|
|
|
// for all events.
|
|
|
|
//
|
|
|
|
// Process must be running in order to handle any events on the connection,
|
|
|
|
// including connection close.
|
|
|
|
//
|
|
|
|
// Process blocks until the connection is closed or until Break() is called.
|
|
|
|
// If the connection is closed, a non-nil error is returned.
|
|
|
|
func (rc *Connection) Process(handler Handler) error {
|
2017-06-27 19:24:04 +00:00
|
|
|
rc.traceLog("entering process loop")
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
rc.processUserCallback(func() { handler.OnReady(rc) })
|
2017-05-02 23:33:51 +00:00
|
|
|
breaked := false
|
|
|
|
for !breaked {
|
|
|
|
|
|
|
|
var packet utils.RicochetData
|
|
|
|
select {
|
|
|
|
case <-rc.unlockChannel:
|
|
|
|
<-rc.unlockResponseChannel
|
|
|
|
continue
|
|
|
|
case <-rc.breakChannel:
|
2017-06-27 19:24:04 +00:00
|
|
|
rc.traceLog("process has ended after break")
|
2017-05-02 23:33:51 +00:00
|
|
|
breaked = true
|
|
|
|
continue
|
|
|
|
case packet = <-rc.packetChannel:
|
|
|
|
break
|
|
|
|
case err := <-rc.errorChannel:
|
|
|
|
rc.Conn.Close()
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
rc.processUserCallback(func() { handler.OnClosed(err) })
|
2017-05-02 23:33:51 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if packet.Channel == 0 {
|
2017-07-04 18:29:11 +00:00
|
|
|
rc.traceLog(fmt.Sprintf("received control packet on channel %d", packet.Channel))
|
2017-05-02 23:33:51 +00:00
|
|
|
res := new(Protocol_Data_Control.Packet)
|
|
|
|
err := proto.Unmarshal(packet.Data[:], res)
|
|
|
|
if err == nil {
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
// Wrap controlPacket in processUserCallback, since it calls out in many
|
|
|
|
// places, and wrapping the rest is harmless.
|
|
|
|
rc.processUserCallback(func() { rc.controlPacket(handler, res) })
|
2017-05-02 23:33:51 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Let's check to see if we have defined this channel.
|
|
|
|
channel, found := rc.channelManager.GetChannel(packet.Channel)
|
|
|
|
if found {
|
|
|
|
if len(packet.Data) == 0 {
|
2017-06-27 19:24:04 +00:00
|
|
|
rc.traceLog(fmt.Sprintf("removing channel %d", packet.Channel))
|
2017-05-02 23:33:51 +00:00
|
|
|
rc.channelManager.RemoveChannel(packet.Channel)
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
rc.processUserCallback(func() { channel.Handler.Closed(utils.ChannelClosedByPeerError) })
|
2017-05-02 23:33:51 +00:00
|
|
|
} else {
|
2017-09-23 22:29:46 +00:00
|
|
|
rc.traceLog(fmt.Sprintf("received packet on %v channel %d", channel.Handler.Type(), packet.Channel))
|
2017-05-02 23:33:51 +00:00
|
|
|
// Send The Ricochet Packet to the Handler
|
Fix and document safety problems with Connection.Do
There were several issues with the Do function that made it nearly
impossible to write safe code.
First, Do cannot be called recursively -- it will deadlock. There is
actually no way to implement a safe and recursive Do (or mutex) in Go,
because there is no primitive that will identify the current goroutine.
RequestOpenChannel used Do internally, which made it impossible to open
channels safely in many circumstances. That has been removed, so all
calls to RequestOpenChannel must be changed to happen under Do now.
Do now has more documentation and a new rule: no code exposed through
API can use Do, unless it has sole custody of the connection (such as
ProcessAuthAsClient).
Related to that problem, Do was impossible to call from inside handlers
(or anything else on the process goroutine) -- it would again just
deadlock. This is resolved by wrapping calls into user code to continue
handling invocations of Do (and only those) while the handler is
executing.
There is a third issue with connection close, but it will be addressed
in a separate commit
And finally, because it's impossible to timeout or interrupt a call to
Do, I also added a DoContext method that takes a go Context, which is
also passed through to the called function.
2017-09-16 19:38:00 +00:00
|
|
|
rc.processUserCallback(func() { channel.Handler.Packet(packet.Data[:]) })
|
2017-05-02 23:33:51 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// When a non-zero packet is received for an unknown
|
|
|
|
// channel, the recipient responds by closing
|
|
|
|
// that channel.
|
2017-07-04 18:29:11 +00:00
|
|
|
rc.traceLog(fmt.Sprintf("received packet on unknown channel %d. closing.", packet.Channel))
|
2017-05-02 23:33:51 +00:00
|
|
|
if len(packet.Data) != 0 {
|
|
|
|
rc.SendRicochetPacket(rc.Conn, packet.Channel, []byte{})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rc.breakResultChannel <- true
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rc *Connection) controlPacket(handler Handler, res *Protocol_Data_Control.Packet) {
|
|
|
|
|
|
|
|
if res.GetOpenChannel() != nil {
|
|
|
|
|
|
|
|
opm := res.GetOpenChannel()
|
|
|
|
chandler, err := handler.OnOpenChannelRequest(opm.GetChannelType())
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
response := rc.messageBuilder.RejectOpenChannel(opm.GetChannelIdentifier(), "UnknownTypeError")
|
|
|
|
rc.SendRicochetPacket(rc.Conn, 0, response)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that we have the authentication already
|
|
|
|
if chandler.RequiresAuthentication() != "none" {
|
2017-07-04 18:29:11 +00:00
|
|
|
rc.traceLog(fmt.Sprintf("channel %v requires authorization of type %v", chandler.Type(), chandler.RequiresAuthentication()))
|
2017-05-02 23:33:51 +00:00
|
|
|
// Enforce Authentication Check.
|
|
|
|
_, authed := rc.Authentication[chandler.RequiresAuthentication()]
|
|
|
|
if !authed {
|
|
|
|
rc.SendRicochetPacket(rc.Conn, 0, []byte{})
|
2017-06-27 19:24:04 +00:00
|
|
|
rc.traceLog(fmt.Sprintf("do not have required authorization to open channel type %v", chandler.Type()))
|
2017-05-02 23:33:51 +00:00
|
|
|
return
|
|
|
|
}
|
2017-06-27 19:24:04 +00:00
|
|
|
rc.traceLog("succeeded authorization check")
|
2017-05-02 23:33:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
channel, err := rc.channelManager.OpenChannelRequestFromPeer(opm.GetChannelIdentifier(), chandler)
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
|
|
channel.SendMessage = func(message []byte) {
|
|
|
|
rc.SendRicochetPacket(rc.Conn, channel.ID, message)
|
|
|
|
}
|
|
|
|
channel.DelegateAuthorization = func() {
|
|
|
|
rc.Authentication[chandler.Type()] = true
|
|
|
|
}
|
|
|
|
channel.CloseChannel = func() {
|
|
|
|
rc.SendRicochetPacket(rc.Conn, channel.ID, []byte{})
|
|
|
|
rc.channelManager.RemoveChannel(channel.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
response, err := chandler.OpenInbound(channel, opm)
|
|
|
|
if err == nil && channel.Pending == false {
|
2017-07-04 18:29:11 +00:00
|
|
|
rc.traceLog(fmt.Sprintf("opening channel %v on %v", channel.Type, channel.ID))
|
2017-05-02 23:33:51 +00:00
|
|
|
rc.SendRicochetPacket(rc.Conn, 0, response)
|
|
|
|
} else {
|
2017-07-04 18:29:11 +00:00
|
|
|
rc.traceLog(fmt.Sprintf("removing channel %v", channel.ID))
|
2017-05-02 23:33:51 +00:00
|
|
|
rc.channelManager.RemoveChannel(channel.ID)
|
|
|
|
rc.SendRicochetPacket(rc.Conn, 0, []byte{})
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Send Error Packet
|
|
|
|
response := rc.messageBuilder.RejectOpenChannel(opm.GetChannelIdentifier(), "GenericError")
|
2017-06-27 19:24:04 +00:00
|
|
|
rc.traceLog(fmt.Sprintf("sending reject open channel for %v", opm.GetChannelIdentifier()))
|
2017-05-02 23:33:51 +00:00
|
|
|
rc.SendRicochetPacket(rc.Conn, 0, response)
|
|
|
|
|
|
|
|
}
|
|
|
|
} else if res.GetChannelResult() != nil {
|
|
|
|
cr := res.GetChannelResult()
|
|
|
|
id := cr.GetChannelIdentifier()
|
|
|
|
|
|
|
|
channel, found := rc.channelManager.GetChannel(id)
|
|
|
|
|
|
|
|
if !found {
|
2017-07-04 18:29:11 +00:00
|
|
|
rc.traceLog(fmt.Sprintf("channel result recived for unknown channel: %v", channel.Type, id))
|
2017-05-02 23:33:51 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if cr.GetOpened() {
|
2017-07-04 18:29:11 +00:00
|
|
|
rc.traceLog(fmt.Sprintf("channel of type %v opened on %v", channel.Type, id))
|
2017-09-23 22:29:46 +00:00
|
|
|
channel.Handler.OpenOutboundResult(nil, cr)
|
2017-05-02 23:33:51 +00:00
|
|
|
} else {
|
2017-07-04 18:29:11 +00:00
|
|
|
rc.traceLog(fmt.Sprintf("channel of type %v rejected on %v", channel.Type, id))
|
2017-09-23 22:29:46 +00:00
|
|
|
channel.Handler.OpenOutboundResult(errors.New(""), cr)
|
2017-05-02 23:33:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} else if res.GetKeepAlive() != nil {
|
|
|
|
// XXX Though not currently part of the protocol
|
|
|
|
// We should likely put these calls behind
|
|
|
|
// authentication.
|
2017-06-27 19:24:04 +00:00
|
|
|
rc.traceLog("received keep alive packet")
|
2017-05-02 23:33:51 +00:00
|
|
|
if res.GetKeepAlive().GetResponseRequested() {
|
|
|
|
messageBuilder := new(utils.MessageBuilder)
|
|
|
|
raw := messageBuilder.KeepAlive(true)
|
2017-06-27 19:24:04 +00:00
|
|
|
rc.traceLog("sending keep alive response")
|
2017-05-02 23:33:51 +00:00
|
|
|
rc.SendRicochetPacket(rc.Conn, 0, raw)
|
|
|
|
}
|
|
|
|
} else if res.GetEnableFeatures() != nil {
|
2017-07-04 18:29:11 +00:00
|
|
|
rc.traceLog("received features enabled packet")
|
2017-05-02 23:33:51 +00:00
|
|
|
messageBuilder := new(utils.MessageBuilder)
|
|
|
|
raw := messageBuilder.FeaturesEnabled([]string{})
|
2017-07-04 18:29:11 +00:00
|
|
|
rc.traceLog("sending featured enabled empty response")
|
2017-05-02 23:33:51 +00:00
|
|
|
rc.SendRicochetPacket(rc.Conn, 0, raw)
|
|
|
|
} else if res.GetFeaturesEnabled() != nil {
|
|
|
|
// TODO We should never send out an enabled features
|
|
|
|
// request.
|
2017-07-04 18:29:11 +00:00
|
|
|
rc.traceLog("sending unsolicited features enabled response")
|
2017-05-02 23:33:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-27 19:24:04 +00:00
|
|
|
func (rc *Connection) traceLog(message string) {
|
2017-07-04 18:29:11 +00:00
|
|
|
if rc.trace {
|
|
|
|
log.Printf(message)
|
|
|
|
}
|
2017-06-27 19:24:04 +00:00
|
|
|
}
|
|
|
|
|
2017-05-02 23:33:51 +00:00
|
|
|
// Break causes Process() to return, but does not close the underlying connection
|
|
|
|
func (rc *Connection) Break() {
|
2017-07-04 18:29:11 +00:00
|
|
|
rc.traceLog("breaking out of process loop")
|
2017-05-02 23:33:51 +00:00
|
|
|
rc.breakChannel <- true
|
|
|
|
<-rc.breakResultChannel // Wait for Process to End
|
|
|
|
}
|
|
|
|
|
|
|
|
// Channel is a convienciance method for returning a given channel to the caller
|
|
|
|
// of Process() - TODO - this is kind of ugly.
|
|
|
|
func (rc *Connection) Channel(ctype string, way channels.Direction) *channels.Channel {
|
|
|
|
return rc.channelManager.Channel(ctype, way)
|
|
|
|
}
|