micro/tunnel/default.go

697 lines
15 KiB
Go
Raw Normal View History

2019-08-05 21:41:48 +03:00
package tunnel
import (
2019-08-07 20:44:33 +03:00
"errors"
2019-08-30 22:05:00 +03:00
"strings"
2019-08-05 21:41:48 +03:00
"sync"
"time"
2019-08-05 21:41:48 +03:00
2019-08-07 20:44:33 +03:00
"github.com/google/uuid"
2019-08-05 21:41:48 +03:00
"github.com/micro/go-micro/transport"
2019-08-07 20:44:33 +03:00
"github.com/micro/go-micro/util/log"
2019-08-05 21:41:48 +03:00
)
var (
// KeepAliveTime defines time interval we send keepalive messages to outbound links
KeepAliveTime = 30 * time.Second
// ReconnectTime defines time interval we periodically attempt to reconnect dead links
ReconnectTime = 5 * time.Second
)
2019-08-07 20:44:33 +03:00
// tun represents a network tunnel
2019-08-05 21:41:48 +03:00
type tun struct {
2019-08-07 20:44:33 +03:00
options Options
2019-08-05 21:41:48 +03:00
sync.RWMutex
2019-08-07 20:44:33 +03:00
2019-08-30 22:05:00 +03:00
// the unique id for this tunnel
id string
// tunnel token for authentication
token string
2019-08-07 20:44:33 +03:00
// to indicate if we're connected or not
2019-08-05 21:41:48 +03:00
connected bool
2019-08-07 20:44:33 +03:00
// the send channel for all messages
send chan *message
// close channel
closed chan bool
2019-08-30 22:05:00 +03:00
// a map of sessions based on Micro-Tunnel-Channel
sessions map[string]*session
2019-08-07 20:44:33 +03:00
// outbound links
links map[string]*link
// listener
listener transport.Listener
2019-08-05 21:41:48 +03:00
}
2019-08-07 20:44:33 +03:00
// create new tunnel on top of a link
func newTunnel(opts ...Option) *tun {
options := DefaultOptions()
2019-08-05 21:41:48 +03:00
for _, o := range opts {
o(&options)
}
2019-08-07 20:44:33 +03:00
return &tun{
2019-08-30 22:05:00 +03:00
options: options,
id: options.Id,
token: options.Token,
send: make(chan *message, 128),
closed: make(chan bool),
sessions: make(map[string]*session),
links: make(map[string]*link),
2019-08-05 21:41:48 +03:00
}
2019-08-07 20:44:33 +03:00
}
2019-08-05 21:41:48 +03:00
2019-08-14 15:26:23 +03:00
// Init initializes tunnel options
func (t *tun) Init(opts ...Option) error {
t.Lock()
defer t.Unlock()
2019-08-14 15:26:23 +03:00
for _, o := range opts {
o(&t.options)
}
return nil
}
2019-08-30 22:05:00 +03:00
// getSession returns a session from the internal session map.
// It does this based on the Micro-Tunnel-Channel and Micro-Tunnel-Session
func (t *tun) getSession(channel, session string) (*session, bool) {
// get the session
2019-08-07 20:44:33 +03:00
t.RLock()
2019-08-30 22:05:00 +03:00
s, ok := t.sessions[channel+session]
2019-08-07 20:44:33 +03:00
t.RUnlock()
return s, ok
2019-08-05 21:41:48 +03:00
}
2019-08-30 22:05:00 +03:00
// newSession creates a new session and saves it
func (t *tun) newSession(channel, sessionId string) (*session, bool) {
// new session
s := &session{
id: t.id,
channel: channel,
session: sessionId,
2019-08-07 20:44:33 +03:00
closed: make(chan bool),
recv: make(chan *message, 128),
send: t.send,
wait: make(chan bool),
2019-08-30 22:05:00 +03:00
errChan: make(chan error, 1),
2019-08-07 20:44:33 +03:00
}
2019-08-30 22:05:00 +03:00
// save session
2019-08-07 20:44:33 +03:00
t.Lock()
2019-08-30 22:05:00 +03:00
_, ok := t.sessions[channel+sessionId]
2019-08-07 20:44:33 +03:00
if ok {
2019-08-30 22:05:00 +03:00
// session already exists
2019-08-07 20:44:33 +03:00
t.Unlock()
return nil, false
}
2019-08-30 22:05:00 +03:00
t.sessions[channel+sessionId] = s
2019-08-07 20:44:33 +03:00
t.Unlock()
2019-08-30 22:05:00 +03:00
// return session
2019-08-07 20:44:33 +03:00
return s, true
2019-08-05 21:41:48 +03:00
}
2019-08-07 20:44:33 +03:00
// TODO: use tunnel id as part of the session
2019-08-30 22:05:00 +03:00
func (t *tun) newSessionId() string {
2019-08-07 20:44:33 +03:00
return uuid.New().String()
2019-08-06 13:45:25 +03:00
}
// monitor monitors outbound links and attempts to reconnect to the failed ones
func (t *tun) monitor() {
reconnect := time.NewTicker(ReconnectTime)
defer reconnect.Stop()
for {
select {
case <-t.closed:
return
case <-reconnect.C:
for _, node := range t.options.Nodes {
t.Lock()
if _, ok := t.links[node]; !ok {
2019-08-15 21:24:24 +03:00
link, err := t.setupLink(node)
if err != nil {
log.Debugf("Tunnel failed to setup node link to %s: %v", node, err)
t.Unlock()
continue
}
t.links[node] = link
}
t.Unlock()
}
}
}
}
2019-08-30 22:05:00 +03:00
// process outgoing messages sent by all local sessions
2019-08-07 20:44:33 +03:00
func (t *tun) process() {
// manage the send buffer
2019-08-30 22:05:00 +03:00
// all pseudo sessions throw everything down this
2019-08-07 20:44:33 +03:00
for {
select {
case msg := <-t.send:
newMsg := &transport.Message{
Header: make(map[string]string),
2019-08-07 20:44:33 +03:00
Body: msg.data.Body,
}
for k, v := range msg.data.Header {
newMsg.Header[k] = v
2019-08-08 14:45:37 +03:00
}
2019-08-20 19:20:21 +03:00
// set message head
2019-08-29 14:42:27 +03:00
newMsg.Header["Micro-Tunnel"] = msg.typ
2019-08-20 19:20:21 +03:00
2019-08-07 20:44:33 +03:00
// set the tunnel id on the outgoing message
newMsg.Header["Micro-Tunnel-Id"] = msg.id
2019-08-07 20:44:33 +03:00
2019-08-30 22:05:00 +03:00
// set the tunnel channel on the outgoing message
newMsg.Header["Micro-Tunnel-Channel"] = msg.channel
2019-08-07 20:44:33 +03:00
// set the session id
newMsg.Header["Micro-Tunnel-Session"] = msg.session
2019-08-07 20:44:33 +03:00
// set the tunnel token
newMsg.Header["Micro-Tunnel-Token"] = t.token
2019-08-07 20:44:33 +03:00
// send the message via the interface
t.Lock()
2019-08-29 14:42:27 +03:00
2019-08-11 20:11:33 +03:00
if len(t.links) == 0 {
log.Debugf("No links to send to")
2019-08-11 20:11:33 +03:00
}
2019-08-29 14:42:27 +03:00
2019-08-30 22:05:00 +03:00
var sent bool
var err error
for node, link := range t.links {
// if the link is not connected skip it
if !link.connected {
log.Debugf("Link for node %s not connected", node)
2019-08-30 22:05:00 +03:00
err = errors.New("link not connected")
continue
}
2019-08-29 14:42:27 +03:00
// if we're picking the link check the id
// this is where we explicitly set the link
// in a message received via the listen method
if len(msg.link) > 0 && link.id != msg.link {
2019-08-30 22:05:00 +03:00
err = errors.New("link not found")
2019-08-29 14:42:27 +03:00
continue
}
// if the link was a loopback accepted connection
// and the message is being sent outbound via
// a dialled connection don't use this link
2019-08-14 19:14:39 +03:00
if link.loopback && msg.outbound {
2019-08-30 22:05:00 +03:00
err = errors.New("link is loopback")
2019-08-14 19:14:39 +03:00
continue
}
// if the message was being returned by the loopback listener
// send it back up the loopback link only
if msg.loopback && !link.loopback {
2019-08-30 22:05:00 +03:00
err = errors.New("link is not loopback")
continue
}
2019-08-29 14:42:27 +03:00
// send the message via the current link
log.Debugf("Sending %+v to %s", newMsg, node)
2019-08-30 22:05:00 +03:00
if errr := link.Send(newMsg); errr != nil {
log.Debugf("Tunnel error sending %+v to %s: %v", newMsg, node, errr)
err = errors.New(errr.Error())
delete(t.links, node)
continue
}
2019-08-30 22:05:00 +03:00
// is sent
sent = true
2019-08-07 20:44:33 +03:00
}
2019-08-29 14:42:27 +03:00
t.Unlock()
2019-08-30 22:05:00 +03:00
var gerr error
if !sent {
gerr = err
}
// return error non blocking
select {
case msg.errChan <- gerr:
default:
}
2019-08-07 20:44:33 +03:00
case <-t.closed:
return
}
}
2019-08-05 21:41:48 +03:00
}
2019-08-07 20:44:33 +03:00
// process incoming messages
2019-08-14 19:14:39 +03:00
func (t *tun) listen(link *link) {
// remove the link on exit
defer func() {
log.Debugf("Tunnel deleting connection from %s", link.Remote())
t.Lock()
delete(t.links, link.Remote())
t.Unlock()
}()
// let us know if its a loopback
var loopback bool
2019-08-07 20:44:33 +03:00
for {
// process anything via the net interface
msg := new(transport.Message)
if err := link.Recv(msg); err != nil {
log.Debugf("Tunnel link %s receive error: %#v", link.Remote(), err)
2019-08-07 20:44:33 +03:00
return
}
2019-08-30 22:05:00 +03:00
// always ensure we have the correct auth token
// TODO: segment the tunnel based on token
// e.g use it as the basis
token := msg.Header["Micro-Tunnel-Token"]
if token != t.token {
log.Debugf("Tunnel link %s received invalid token %s", token)
return
}
switch msg.Header["Micro-Tunnel"] {
case "connect":
log.Debugf("Tunnel link %s received connect message", link.Remote())
2019-08-30 22:05:00 +03:00
id := msg.Header["Micro-Tunnel-Id"]
// are we connecting to ourselves?
2019-08-30 22:05:00 +03:00
if id == t.id {
2019-08-14 19:14:39 +03:00
link.loopback = true
loopback = true
}
2019-08-20 19:20:21 +03:00
// set as connected
link.connected = true
// save the link once connected
t.Lock()
t.links[link.Remote()] = link
t.Unlock()
2019-08-20 19:20:21 +03:00
// nothing more to do
continue
case "close":
log.Debugf("Tunnel link %s closing connection", link.Remote())
// TODO: handle the close message
// maybe report io.EOF or kill the link
2019-08-29 14:42:27 +03:00
return
case "keepalive":
log.Debugf("Tunnel link %s received keepalive", link.Remote())
t.Lock()
2019-08-29 14:42:27 +03:00
// save the keepalive
link.lastKeepAlive = time.Now()
t.Unlock()
continue
2019-08-20 19:20:21 +03:00
case "message":
// process message
log.Debugf("Received %+v from %s", msg, link.Remote())
default:
// blackhole it
continue
2019-08-07 20:44:33 +03:00
}
// if its not connected throw away the link
if !link.connected {
2019-08-29 14:42:27 +03:00
log.Debugf("Tunnel link %s not connected", link.id)
return
}
2019-08-07 20:44:33 +03:00
// the tunnel id
id := msg.Header["Micro-Tunnel-Id"]
2019-08-30 22:05:00 +03:00
// the tunnel channel
channel := msg.Header["Micro-Tunnel-Channel"]
2019-08-07 20:44:33 +03:00
// the session id
2019-08-30 22:05:00 +03:00
sessionId := msg.Header["Micro-Tunnel-Session"]
2019-08-07 20:44:33 +03:00
2019-08-30 22:05:00 +03:00
// strip tunnel message header
for k, _ := range msg.Header {
if strings.HasPrefix(k, "Micro-Tunnel") {
delete(msg.Header, k)
}
}
2019-08-20 19:20:21 +03:00
2019-08-07 20:44:33 +03:00
// if the session id is blank there's nothing we can do
// TODO: check this is the case, is there any reason
// why we'd have a blank session? Is the tunnel
// used for some other purpose?
2019-08-30 22:05:00 +03:00
if len(channel) == 0 || len(sessionId) == 0 {
2019-08-07 20:44:33 +03:00
continue
}
2019-08-30 22:05:00 +03:00
var s *session
2019-08-07 20:44:33 +03:00
var exists bool
// If its a loopback connection then we've enabled link direction
// listening side is used for listening, the dialling side for dialling
switch {
case loopback:
2019-08-30 22:05:00 +03:00
s, exists = t.getSession(channel, "listener")
default:
2019-08-30 22:05:00 +03:00
// get the session based on the tunnel id and session
// this could be something we dialed in which case
// we have a session for it otherwise its a listener
2019-08-30 22:05:00 +03:00
s, exists = t.getSession(channel, sessionId)
if !exists {
// try get it based on just the tunnel id
// the assumption here is that a listener
// has no session but its set a listener session
2019-08-30 22:05:00 +03:00
s, exists = t.getSession(channel, "listener")
}
2019-08-07 20:44:33 +03:00
}
2019-08-30 22:05:00 +03:00
// bail if no session has been found
2019-08-07 20:44:33 +03:00
if !exists {
2019-08-30 22:05:00 +03:00
log.Debugf("Tunnel skipping no session exists")
2019-08-07 20:44:33 +03:00
// drop it, we don't care about
// messages we don't know about
continue
}
2019-08-30 22:05:00 +03:00
log.Debugf("Tunnel using session %s %s", s.channel, s.session)
// is the session closed?
2019-08-07 20:44:33 +03:00
select {
case <-s.closed:
// closed
2019-08-30 22:05:00 +03:00
delete(t.sessions, channel)
2019-08-07 20:44:33 +03:00
continue
default:
// process
}
2019-08-30 22:05:00 +03:00
// is the session new?
2019-08-07 20:44:33 +03:00
select {
2019-08-30 22:05:00 +03:00
// if its new the session is actually blocked waiting
2019-08-07 20:44:33 +03:00
// for a connection. so we check if its waiting.
case <-s.wait:
// if its waiting e.g its new then we close it
default:
2019-08-30 22:05:00 +03:00
// set remote address of the session
2019-08-07 20:44:33 +03:00
s.remote = msg.Header["Remote"]
close(s.wait)
}
// construct a new transport message
tmsg := &transport.Message{
Header: msg.Header,
Body: msg.Body,
}
// construct the internal message
imsg := &message{
id: id,
2019-08-30 22:05:00 +03:00
channel: channel,
session: sessionId,
data: tmsg,
2019-08-29 14:42:27 +03:00
link: link.id,
loopback: loopback,
2019-08-30 22:05:00 +03:00
errChan: make(chan error, 1),
2019-08-07 20:44:33 +03:00
}
// append to recv backlog
// we don't block if we can't pass it on
select {
case s.recv <- imsg:
default:
}
}
2019-08-05 21:41:48 +03:00
}
// keepalive periodically sends keepalive messages to link
func (t *tun) keepalive(link *link) {
keepalive := time.NewTicker(KeepAliveTime)
defer keepalive.Stop()
for {
select {
case <-t.closed:
return
case <-keepalive.C:
// send keepalive message
log.Debugf("Tunnel sending keepalive to link: %v", link.Remote())
if err := link.Send(&transport.Message{
Header: map[string]string{
"Micro-Tunnel": "keepalive",
2019-08-30 22:05:00 +03:00
"Micro-Tunnel-Id": t.id,
"Micro-Tunnel-Token": t.token,
},
}); err != nil {
log.Debugf("Error sending keepalive to link %v: %v", link.Remote(), err)
t.Lock()
delete(t.links, link.Remote())
t.Unlock()
return
}
}
}
}
2019-08-15 21:24:24 +03:00
// setupLink connects to node and returns link if successful
// It returns error if the link failed to be established
func (t *tun) setupLink(node string) (*link, error) {
2019-08-29 18:58:07 +03:00
log.Debugf("Tunnel setting up link: %s", node)
c, err := t.options.Transport.Dial(node)
if err != nil {
log.Debugf("Tunnel failed to connect to %s: %v", node, err)
return nil, err
}
log.Debugf("Tunnel connected to %s", node)
if err := c.Send(&transport.Message{
Header: map[string]string{
"Micro-Tunnel": "connect",
2019-08-30 22:05:00 +03:00
"Micro-Tunnel-Id": t.id,
"Micro-Tunnel-Token": t.token,
},
}); err != nil {
return nil, err
}
// create a new link
2019-08-29 14:42:27 +03:00
link := newLink(c)
link.connected = true
// we made the outbound connection
// and sent the connect message
// process incoming messages
go t.listen(link)
// start keepalive monitor
go t.keepalive(link)
return link, nil
}
2019-08-14 15:26:23 +03:00
// connect the tunnel to all the nodes and listen for incoming tunnel connections
2019-08-07 20:44:33 +03:00
func (t *tun) connect() error {
l, err := t.options.Transport.Listen(t.options.Address)
if err != nil {
return err
}
// save the listener
t.listener = l
go func() {
// accept inbound connections
err := l.Accept(func(sock transport.Socket) {
2019-08-08 15:15:30 +03:00
log.Debugf("Tunnel accepted connection from %s", sock.Remote())
// create a new link
2019-08-29 14:42:27 +03:00
link := newLink(sock)
2019-08-07 20:44:33 +03:00
// listen for inbound messages.
// only save the link once connected.
// we do this inside liste
2019-08-14 19:14:39 +03:00
t.listen(link)
2019-08-07 20:44:33 +03:00
})
2019-08-29 14:42:27 +03:00
t.RLock()
defer t.RUnlock()
2019-08-07 20:44:33 +03:00
// still connected but the tunnel died
if err != nil && t.connected {
log.Logf("Tunnel listener died: %v", err)
}
}()
for _, node := range t.options.Nodes {
2019-08-08 15:15:30 +03:00
// skip zero length nodes
if len(node) == 0 {
continue
}
// connect to node and return link
2019-08-15 21:24:24 +03:00
link, err := t.setupLink(node)
2019-08-07 20:44:33 +03:00
if err != nil {
log.Debugf("Tunnel failed to establish node link to %s: %v", node, err)
2019-08-07 20:44:33 +03:00
continue
}
2019-08-07 20:44:33 +03:00
// save the link
t.links[node] = link
2019-08-07 20:44:33 +03:00
}
// process outbound messages to be sent
// process sends to all links
go t.process()
// monitor links
go t.monitor()
2019-08-05 21:41:48 +03:00
return nil
}
2019-08-14 15:26:23 +03:00
// Connect the tunnel
func (t *tun) Connect() error {
t.Lock()
defer t.Unlock()
// already connected
if t.connected {
return nil
}
// send the connect message
if err := t.connect(); err != nil {
return err
}
// set as connected
t.connected = true
// create new close channel
t.closed = make(chan bool)
return nil
}
2019-08-07 20:44:33 +03:00
func (t *tun) close() error {
// close all the links
for node, link := range t.links {
2019-08-07 20:44:33 +03:00
link.Send(&transport.Message{
Header: map[string]string{
"Micro-Tunnel": "close",
2019-08-30 22:05:00 +03:00
"Micro-Tunnel-Id": t.id,
"Micro-Tunnel-Token": t.token,
2019-08-07 20:44:33 +03:00
},
})
link.Close()
delete(t.links, node)
2019-08-07 20:44:33 +03:00
}
// close the listener
return t.listener.Close()
2019-08-05 21:41:48 +03:00
}
2019-08-21 14:55:10 +03:00
func (t *tun) Address() string {
t.RLock()
defer t.RUnlock()
if !t.connected {
return t.options.Address
}
return t.listener.Addr()
}
2019-08-07 20:44:33 +03:00
// Close the tunnel
func (t *tun) Close() error {
t.Lock()
defer t.Unlock()
if !t.connected {
return nil
}
2019-08-05 21:41:48 +03:00
select {
case <-t.closed:
2019-08-07 20:44:33 +03:00
return nil
2019-08-05 21:41:48 +03:00
default:
2019-08-30 22:05:00 +03:00
// close all the sessions
for id, s := range t.sessions {
2019-08-07 20:44:33 +03:00
s.Close()
2019-08-30 22:05:00 +03:00
delete(t.sessions, id)
2019-08-07 20:44:33 +03:00
}
// close the connection
close(t.closed)
t.connected = false
// send a close message
// we don't close the link
// just the tunnel
return t.close()
2019-08-05 21:41:48 +03:00
}
2019-08-07 20:44:33 +03:00
return nil
2019-08-05 21:41:48 +03:00
}
2019-08-07 20:44:33 +03:00
// Dial an address
2019-08-30 22:05:00 +03:00
func (t *tun) Dial(channel string) (Session, error) {
log.Debugf("Tunnel dialing %s", channel)
c, ok := t.newSession(channel, t.newSessionId())
2019-08-07 20:44:33 +03:00
if !ok {
2019-08-30 22:05:00 +03:00
return nil, errors.New("error dialing " + channel)
2019-08-07 20:44:33 +03:00
}
// set remote
2019-08-30 22:05:00 +03:00
c.remote = channel
2019-08-07 20:44:33 +03:00
// set local
c.local = "local"
2019-08-30 22:05:00 +03:00
// outbound session
2019-08-14 19:14:39 +03:00
c.outbound = true
2019-08-07 20:44:33 +03:00
return c, nil
}
// Accept a connection on the address
2019-08-30 22:05:00 +03:00
func (t *tun) Listen(channel string) (Listener, error) {
log.Debugf("Tunnel listening on %s", channel)
// create a new session by hashing the address
c, ok := t.newSession(channel, "listener")
2019-08-07 20:44:33 +03:00
if !ok {
2019-08-30 22:05:00 +03:00
return nil, errors.New("already listening on " + channel)
2019-08-07 20:44:33 +03:00
}
// set remote. it will be replaced by the first message received
c.remote = "remote"
// set local
2019-08-30 22:05:00 +03:00
c.local = channel
2019-08-07 20:44:33 +03:00
tl := &tunListener{
2019-08-30 22:05:00 +03:00
channel: channel,
2019-08-07 20:44:33 +03:00
// the accept channel
2019-08-30 22:05:00 +03:00
accept: make(chan *session, 128),
2019-08-07 20:44:33 +03:00
// the channel to close
closed: make(chan bool),
// tunnel closed channel
tunClosed: t.closed,
2019-08-30 22:05:00 +03:00
// the listener session
session: c,
2019-08-07 20:44:33 +03:00
}
// this kicks off the internal message processor
2019-08-30 22:05:00 +03:00
// for the listener so it can create pseudo sessions
2019-08-07 20:44:33 +03:00
// per session if they do not exist or pass messages
// to the existign sessions
go tl.process()
// return the listener
return tl, nil
2019-08-05 21:41:48 +03:00
}
2019-08-20 19:21:35 +03:00
func (t *tun) String() string {
return "mucp"
}