telebit/mplexer/mplexer.go

161 lines
3.5 KiB
Go

package mplexer
import (
"context"
"fmt"
"io"
"net"
"os"
"time"
"git.coolaj86.com/coolaj86/go-telebitd/mplexer/packer"
)
type MultiplexLocal struct {
Relay string
SortingHat SortingHat
Timeout time.Duration
listener *Listener
}
func New(relay string, hat SortingHat) *MultiplexLocal {
return &MultiplexLocal{
Relay: relay,
SortingHat: hat,
Timeout: 30 * time.Second,
}
}
func (m *MultiplexLocal) ListenAndServe(ctx context.Context) error {
// Cancels if Accept() returns an error (i.e. because it was closed)
// (TODO: this may be redundant)
ctx, cancel := context.WithCancel(ctx)
defer cancel()
listener, err := m.Listen(ctx)
if nil != err {
return err
}
for {
pconn, err := listener.Accept() // packer.Conn
if nil != err {
return err
}
go m.serve(ctx, pconn)
}
}
func (m *MultiplexLocal) Close() error {
return m.listener.Close()
}
func (m *MultiplexLocal) serve(ctx context.Context, pconn *packer.Conn) {
//paddr := pconn.LocalAddr().(*Addr) // packer.Addr
paddr := pconn.LocalAddr()
//addr.Network()
//addr.String()
paddr.Scheme()
//paddr.Encrypted()
//paddr.Servername()
// todo: some sort of logic to avoid infinite loop to self?
// (that's probably not possible since the connection could
// route several layers deep)
if target, err := m.SortingHat.LookupTarget(paddr); nil != target {
if nil != err {
// TODO get a log channel or some such
fmt.Fprintf(os.Stderr, "lookup failed for tunneled client: %s\n", err)
err := pconn.Error(err)
if nil != err {
fmt.Fprintf(os.Stderr, "failed to signal error back to relay: %s\n", err)
}
return
}
pipePacker(ctx, pconn, target, m.Timeout)
}
}
func pipePacker(ctx context.Context, pconn *packer.Conn, target net.Conn, timeout time.Duration) {
// how can this be done so that target errors are
// sent back to the relay server?
// Also something like ReadAhead(size) should signal
// to read and send up to `size` bytes without waiting
// for a response - since we can't signal 'non-read' as
// is the normal operation of tcp... or can we?
// And how do we distinguish idle from dropped?
// Maybe this should have been a udp protocol???
defer pconn.Close()
defer target.Close()
srcCh := make(chan []byte)
dstCh := make(chan []byte)
errCh := make(chan error)
// Source (Relay) Read Channel
go func() {
// TODO what's the optimal size to buffer?
// TODO user buffered reader
b := make([]byte, 128*1024)
for {
pconn.SetDeadline(time.Now().Add(timeout))
n, err := pconn.Read(b)
if n > 0 {
srcCh <- b
}
if nil != err {
// TODO let client log this server-side error (unless EOF)
// (nil here because we probably can't send the error to the relay)
errCh <- nil
break
}
}
}()
// Target (Local) Read Channel
go func() {
// TODO what's the optimal size to buffer?
// TODO user buffered reader
b := make([]byte, 128*1024)
for {
target.SetDeadline(time.Now().Add(timeout))
n, err := target.Read(b)
if n > 0 {
dstCh <- b
}
if nil != err {
if io.EOF == err {
err = nil
}
errCh <- err
break
}
}
}()
for {
select {
case <-ctx.Done():
break
case b := <-srcCh:
target.SetDeadline(time.Now().Add(timeout))
_, err := target.Write(b)
if nil != err {
// TODO log error locally
pconn.Error(err)
break
}
case b := <-dstCh:
pconn.SetDeadline(time.Now().Add(timeout))
_, err := pconn.Write(b)
if nil != err {
// TODO log error locally
break
}
}
}
}