wip: new client parse test
This commit is contained in:
parent
581ed296ac
commit
c569a965e2
|
@ -2,10 +2,15 @@ package mplexer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.coolaj86.com/coolaj86/go-telebitd/mplexer/packer"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
@ -13,11 +18,13 @@ import (
|
||||||
// Listener defines a listener for use with http servers
|
// Listener defines a listener for use with http servers
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
//ParentAddr net.Addr
|
//ParentAddr net.Addr
|
||||||
Conns chan *Conn
|
//Conns chan *Conn
|
||||||
ws *websocket.Conn
|
ws *websocket.Conn
|
||||||
|
ctx context.Context
|
||||||
|
parser *packer.Parser
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewListener creates a channel for connections and returns the listener
|
// Listen creates a channel for connections and returns the listener
|
||||||
func (m *MultiplexLocal) Listen(ctx context.Context) (*Listener, error) {
|
func (m *MultiplexLocal) Listen(ctx context.Context) (*Listener, error) {
|
||||||
authz, err := m.SortingHat.Authz()
|
authz, err := m.SortingHat.Authz()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
|
@ -32,32 +39,145 @@ func (m *MultiplexLocal) Listen(ctx context.Context) (*Listener, error) {
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//conns := make(chan *packer.Conn)
|
||||||
|
//parser := &packer.NewParser(ctx, conns)
|
||||||
|
|
||||||
|
/*
|
||||||
|
go func() {
|
||||||
|
conn, err := packer.Accept()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to accept new relayed connection: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conns <- conn
|
||||||
|
}()
|
||||||
|
*/
|
||||||
|
|
||||||
|
handler := &Handler{}
|
||||||
listener := &Listener{
|
listener := &Listener{
|
||||||
Conns: make(chan *Conn),
|
//Conns: conns,
|
||||||
|
parser: packer.NewParser(ctx, handler),
|
||||||
}
|
}
|
||||||
|
go m.listen(ctx, wsconn, listener)
|
||||||
return listener, nil
|
return listener, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) WriteMessage(packer.Addr, []byte) {
|
||||||
|
panic(errors.New("not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MultiplexLocal) listen(ctx context.Context, wsconn *websocket.Conn, listener *Listener) {
|
||||||
|
// will cancel if ws errors out or closes
|
||||||
|
// (TODO: this may also be redundant)
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Ping every 15 seconds, or stop listening
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
deadline := time.Now().Add(45 * time.Second)
|
||||||
|
if err := wsconn.WriteControl(websocket.PingMessage, "", deadline); nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to write ping message to websocket: %s\n", err)
|
||||||
|
cancel()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// The write loop (which fails if ping fails)
|
||||||
|
go func() {
|
||||||
|
// TODO optimal buffer size
|
||||||
|
b := make([]byte, 128*1024)
|
||||||
|
for {
|
||||||
|
n, err := listener.packer.Read(b)
|
||||||
|
if n > 0 {
|
||||||
|
if err := wsconn.WriteMessage(websocket.BinaryMessage, b); nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to write packer message to websocket: %s\n", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nil != err {
|
||||||
|
if io.EOF != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to read message from packer: %s\n", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "[TODO debug] closed packer: %s\n", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO handle EOF as websocket.CloseNormal?
|
||||||
|
message := websocket.FormatCloseMessage(websocket.CloseGoingAway, "closing connection")
|
||||||
|
deadline := time.Now().Add(10 * time.Second)
|
||||||
|
if err := wsconn.WriteControl(websocket.CloseMessage, message, deadline); nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to write close message to websocket: %s\n", err)
|
||||||
|
}
|
||||||
|
_ = wsconn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// The read loop (also fails if ping fails)
|
||||||
|
for {
|
||||||
|
_, message, err := wsconn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to read message from websocket: %s\n", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
_, err = listener.packer.Write(message)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to process message from websocket: %s\n", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// just to be sure
|
||||||
|
listener.packer.Close()
|
||||||
|
wsconn.Close()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
// Feed will block while pushing a net.Conn onto Conns
|
// Feed will block while pushing a net.Conn onto Conns
|
||||||
func (l *Listener) Feed(conn *Conn) {
|
func (l *Listener) Feed(conn *Conn) {
|
||||||
l.Conns <- conn
|
l.Conns <- conn
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// net.Listener interface
|
// net.Listener interface
|
||||||
|
|
||||||
|
/*
|
||||||
// Accept will block and wait for a new net.Conn
|
// Accept will block and wait for a new net.Conn
|
||||||
func (l *Listener) Accept() (*Conn, error) {
|
func (l *Listener) Accept() (*Conn, error) {
|
||||||
conn, ok := <-l.Conns
|
select {
|
||||||
if ok {
|
case conn, ok := <-l.Conns:
|
||||||
return conn, nil
|
if ok {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
return nil, io.EOF
|
||||||
|
|
||||||
|
case <-l.ctx.Done():
|
||||||
|
// TODO is another error more suitable?
|
||||||
|
// TODO is this redundant?
|
||||||
|
return nil, io.EOF
|
||||||
}
|
}
|
||||||
return nil, io.EOF
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (l *Listener) Accept() (*packer.Conn, error) {
|
||||||
|
return l.Accept()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close will close the Conns channel
|
// Close will close the Conns channel
|
||||||
func (l *Listener) Close() error {
|
func (l *Listener) Close() error {
|
||||||
close(l.Conns)
|
//close(l.Conns)
|
||||||
return nil
|
//return nil
|
||||||
|
return l.packer.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Addr returns nil to fulfill the net.Listener interface
|
// Addr returns nil to fulfill the net.Listener interface
|
||||||
|
|
|
@ -7,12 +7,15 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.coolaj86.com/coolaj86/go-telebitd/mplexer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MultiplexLocal struct {
|
type MultiplexLocal struct {
|
||||||
Relay string
|
Relay string
|
||||||
SortingHat SortingHat
|
SortingHat SortingHat
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
|
listener *Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(relay string, hat SortingHat) *MultiplexLocal {
|
func New(relay string, hat SortingHat) *MultiplexLocal {
|
||||||
|
@ -24,6 +27,11 @@ func New(relay string, hat SortingHat) *MultiplexLocal {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MultiplexLocal) ListenAndServe(ctx context.Context) error {
|
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)
|
listener, err := m.Listen(ctx)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return err
|
return err
|
||||||
|
@ -39,7 +47,11 @@ func (m *MultiplexLocal) ListenAndServe(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MultiplexLocal) serve(ctx context.Context, pconn *Conn) {
|
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) // packer.Addr
|
||||||
paddr := pconn.LocalAddr()
|
paddr := pconn.LocalAddr()
|
||||||
//addr.Network()
|
//addr.Network()
|
||||||
|
@ -54,10 +66,10 @@ func (m *MultiplexLocal) serve(ctx context.Context, pconn *Conn) {
|
||||||
if target, err := m.SortingHat.LookupTarget(paddr); nil != target {
|
if target, err := m.SortingHat.LookupTarget(paddr); nil != target {
|
||||||
if nil != err {
|
if nil != err {
|
||||||
// TODO get a log channel or some such
|
// TODO get a log channel or some such
|
||||||
fmt.Fprintf(os.Stderr, "lookup failed for tunneled client: %s", err)
|
fmt.Fprintf(os.Stderr, "lookup failed for tunneled client: %s\n", err)
|
||||||
err := pconn.Error(err)
|
err := pconn.Error(err)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
fmt.Fprintf(os.Stderr, "failed to signal error back to relay: %s", err)
|
fmt.Fprintf(os.Stderr, "failed to signal error back to relay: %s\n", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -65,7 +77,7 @@ func (m *MultiplexLocal) serve(ctx context.Context, pconn *Conn) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func pipePacker(ctx context.Context, pconn *Conn, target net.Conn, timeout time.Duration) {
|
func pipePacker(ctx context.Context, pconn *packer.Conn, target net.Conn, timeout time.Duration) {
|
||||||
// how can this be done so that target errors are
|
// how can this be done so that target errors are
|
||||||
// sent back to the relay server?
|
// sent back to the relay server?
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package mplexer
|
package packer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -24,6 +24,7 @@ const (
|
||||||
type Addr struct {
|
type Addr struct {
|
||||||
scheme Scheme
|
scheme Scheme
|
||||||
termination Termination
|
termination Termination
|
||||||
|
family string // TODO what should be the format? "tcpv6"?
|
||||||
addr string
|
addr string
|
||||||
port int
|
port int
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package mplexer
|
package packer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -7,12 +7,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
// TODO
|
relaySourceAddr Addr
|
||||||
relayRemoteAddr string
|
relayRemoteAddr Addr
|
||||||
relayRemotePort int
|
relay net.Conn
|
||||||
relaySourceProto string
|
local net.Conn
|
||||||
relaySourceAddr string
|
updated time.Time
|
||||||
relaySourcePort int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO conn.go -> conn/conn.go
|
// TODO conn.go -> conn/conn.go
|
||||||
|
@ -26,23 +25,20 @@ func NewConn() *Conn {
|
||||||
// Read can be made to time out and return an Error with Timeout() == true
|
// Read can be made to time out and return an Error with Timeout() == true
|
||||||
// after a fixed time limit; see SetDeadline and SetReadDeadline.
|
// after a fixed time limit; see SetDeadline and SetReadDeadline.
|
||||||
func (c *Conn) Read(b []byte) (n int, err error) {
|
func (c *Conn) Read(b []byte) (n int, err error) {
|
||||||
panic(errors.New("not implemented"))
|
return c.relay.Read(b)
|
||||||
return 0, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes data to the connection.
|
// Write writes data to the connection.
|
||||||
// Write can be made to time out and return an Error with Timeout() == true
|
// Write can be made to time out and return an Error with Timeout() == true
|
||||||
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
|
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
|
||||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||||
panic(errors.New("not implemented"))
|
return c.relay.Write(b)
|
||||||
return 0, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the connection.
|
// Close closes the connection.
|
||||||
// Any blocked Read or Write operations will be unblocked and return errors.
|
// Any blocked Read or Write operations will be unblocked and return errors.
|
||||||
func (c *Conn) Close() error {
|
func (c *Conn) Close() error {
|
||||||
panic(errors.New("not implemented"))
|
return c.relay.Close()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error signals an error back to the relay
|
// Error signals an error back to the relay
|
||||||
|
@ -61,14 +57,14 @@ func (c *Conn) LocalAddr() net.Addr {
|
||||||
|
|
||||||
// LocalAddr returns the local network address.
|
// LocalAddr returns the local network address.
|
||||||
func (c *Conn) LocalAddr() *Addr {
|
func (c *Conn) LocalAddr() *Addr {
|
||||||
panic(errors.New("not implemented"))
|
// TODO is this the right one?
|
||||||
return &Addr{}
|
return &c.relaySourceAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoteAddr returns the remote network address.
|
// RemoteAddr returns the remote network address.
|
||||||
func (c *Conn) RemoteAddr() net.Addr {
|
func (c *Conn) RemoteAddr() net.Addr {
|
||||||
panic(errors.New("not implemented"))
|
// TODO is this the right one?
|
||||||
return &net.IPAddr{}
|
return &c.relayRemoteAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDeadline sets the read and write deadlines associated
|
// SetDeadline sets the read and write deadlines associated
|
||||||
|
@ -94,16 +90,14 @@ func (c *Conn) RemoteAddr() net.Addr {
|
||||||
// failure on I/O can be detected using
|
// failure on I/O can be detected using
|
||||||
// errors.Is(err, syscall.ETIMEDOUT).
|
// errors.Is(err, syscall.ETIMEDOUT).
|
||||||
func (c *Conn) SetDeadline(t time.Time) error {
|
func (c *Conn) SetDeadline(t time.Time) error {
|
||||||
panic(errors.New("not implemented"))
|
return c.relay.SetDeadline(t)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetReadDeadline sets the deadline for future Read calls
|
// SetReadDeadline sets the deadline for future Read calls
|
||||||
// and any currently-blocked Read call.
|
// and any currently-blocked Read call.
|
||||||
// A zero value for t means Read will not time out.
|
// A zero value for t means Read will not time out.
|
||||||
func (c *Conn) SetReadDeadline(t time.Time) error {
|
func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||||
panic(errors.New("not implemented"))
|
return c.relay.SetReadDeadline(t)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetWriteDeadline sets the deadline for future Write calls
|
// SetWriteDeadline sets the deadline for future Write calls
|
||||||
|
@ -112,6 +106,5 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||||
// some of the data was successfully written.
|
// some of the data was successfully written.
|
||||||
// A zero value for t means Write will not time out.
|
// A zero value for t means Write will not time out.
|
||||||
func (c *Conn) SetWriteDeadline(t time.Time) error {
|
func (c *Conn) SetWriteDeadline(t time.Time) error {
|
||||||
panic(errors.New("not implemented"))
|
return c.relay.SetWriteDeadline(t)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package packer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Marshal(addr Addr, body []byte) ([]byte, []byte) {
|
||||||
|
header := []byte(`IPv4,192.168.1.101,6743,` + strconv.Itoa(len(body)) + `,http,80,ex1.telebit.io`)
|
||||||
|
raw := []byte{255 - 1, byte(len(header))}
|
||||||
|
header = append(raw, header...)
|
||||||
|
return header, body
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package packer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Parser struct {
|
||||||
|
ctx context.Context
|
||||||
|
handler Handler
|
||||||
|
newConns chan *Conn
|
||||||
|
conns map[string]*Conn
|
||||||
|
state ParserState
|
||||||
|
parseState State
|
||||||
|
dataReady chan struct{}
|
||||||
|
data []byte
|
||||||
|
written int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParserState struct {
|
||||||
|
written int
|
||||||
|
version byte
|
||||||
|
headerLen int
|
||||||
|
header []byte
|
||||||
|
payloadLen int
|
||||||
|
addr Addr
|
||||||
|
payloadWritten int
|
||||||
|
}
|
||||||
|
|
||||||
|
type State int
|
||||||
|
|
||||||
|
const (
|
||||||
|
V1 byte = 255 - (1 + iota)
|
||||||
|
V2
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
VersionState State = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewParser(ctx context.Context, handler Handler) *Parser {
|
||||||
|
return &Parser{
|
||||||
|
ctx: ctx,
|
||||||
|
conns: make(map[string]*Conn),
|
||||||
|
newConns: make(chan *Conn, 2), // Buffered to make testing easier
|
||||||
|
dataReady: make(chan struct{}, 2),
|
||||||
|
data: []byte{},
|
||||||
|
handler: handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handler interface {
|
||||||
|
WriteMessage(Addr, []byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write receives tunnel data and creates or writes to connections
|
||||||
|
func (p *Parser) Write(b []byte) (int, error) {
|
||||||
|
if len(b) < 1 {
|
||||||
|
return 0, errors.New("developer error: wrote 0 bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
// so that we can overwrite the main state
|
||||||
|
// as soon as a full message has completed
|
||||||
|
// but still keep the number of bytes written
|
||||||
|
if 0 == p.state.written {
|
||||||
|
p.written = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
switch p.parseState {
|
||||||
|
case VersionState:
|
||||||
|
fmt.Println("version state", b[0])
|
||||||
|
p.state.version = b[0]
|
||||||
|
b = b[1:]
|
||||||
|
p.state.written += 1
|
||||||
|
p.parseState += 1
|
||||||
|
default:
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
switch p.state.version {
|
||||||
|
case V1:
|
||||||
|
fmt.Println("v1 unmarshal")
|
||||||
|
return p.written, p.unpackV1(b)
|
||||||
|
default:
|
||||||
|
return 0, errors.New("incorrect version or version not implemented")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package packer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testHandler struct {
|
||||||
|
conns map[string]*Conn
|
||||||
|
chunksParsed int
|
||||||
|
bytesRead int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *testHandler) WriteMessage(a Addr, b []byte) {
|
||||||
|
addr := &a
|
||||||
|
_, ok := th.conns[addr.Network()]
|
||||||
|
if !ok {
|
||||||
|
rconn, wconn := net.Pipe()
|
||||||
|
conn := &Conn{
|
||||||
|
updated: time.Now(),
|
||||||
|
relayRemoteAddr: *addr,
|
||||||
|
relay: rconn,
|
||||||
|
local: wconn,
|
||||||
|
}
|
||||||
|
th.conns[addr.Network()] = conn
|
||||||
|
}
|
||||||
|
th.chunksParsed += 1
|
||||||
|
th.bytesRead += len(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseWholeBlock(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
//ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
th := &testHandler{
|
||||||
|
conns: map[string]*Conn{},
|
||||||
|
}
|
||||||
|
|
||||||
|
p := NewParser(ctx, th)
|
||||||
|
body := []byte(`Hello, World!`)
|
||||||
|
fmt.Println("payload len", len(body))
|
||||||
|
header := []byte("IPv4,192.168.1.101,6743," + strconv.Itoa(len(body)) + ",http,80,ex1.telebit.io,\n")
|
||||||
|
fmt.Println("header len", len(header))
|
||||||
|
raw := []byte{255 - 1, byte(len(header))}
|
||||||
|
raw = append(raw, header...)
|
||||||
|
raw = append(raw, body...)
|
||||||
|
fmt.Println("total len", len(raw))
|
||||||
|
n, err := p.Write(raw)
|
||||||
|
if nil != err {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if 1 != len(th.conns) {
|
||||||
|
t.Fatal("should have parsed one connection")
|
||||||
|
}
|
||||||
|
if 1 != th.chunksParsed {
|
||||||
|
t.Fatal("should have parsed one chunck")
|
||||||
|
}
|
||||||
|
if len(body) != th.bytesRead {
|
||||||
|
t.Fatalf("should have parsed a body of %d bytes, but saw %d\n", len(body), th.bytesRead)
|
||||||
|
}
|
||||||
|
if n != len(raw) {
|
||||||
|
t.Fatalf("should have parsed all %d bytes, not just %d\n", n, len(raw))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package packer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
ctx context.Context
|
||||||
|
newConns chan *Conn
|
||||||
|
data []byte
|
||||||
|
dataReady chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Accept() (*Conn, error) {
|
||||||
|
select {
|
||||||
|
case <-s.ctx.Done():
|
||||||
|
return nil, errors.New("TODO: ErrClosed")
|
||||||
|
case conn := <-s.newConns:
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read packs transforms local responses into wrapped data for the tunnel
|
||||||
|
func (s *Server) Read(b []byte) (int, error) {
|
||||||
|
select {
|
||||||
|
case <-s.ctx.Done():
|
||||||
|
return 0, errors.New("TODO: EOF / ErrClosed")
|
||||||
|
case <-s.dataReady:
|
||||||
|
if 0 == len(s.data) {
|
||||||
|
return s.Read(b)
|
||||||
|
}
|
||||||
|
return s.read(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) read(b []byte) (int, error) {
|
||||||
|
// TODO mutex data while reading, against writing?
|
||||||
|
|
||||||
|
c := len(b) // capacity
|
||||||
|
a := len(s.data) // available
|
||||||
|
n := c
|
||||||
|
|
||||||
|
// see if the available data is smaller than the receiving buffer
|
||||||
|
if a < c {
|
||||||
|
n = a
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy available data up to capacity
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
b[i] = s.data[i]
|
||||||
|
}
|
||||||
|
// shrink the data slice by amount read
|
||||||
|
s.data = s.data[n:]
|
||||||
|
|
||||||
|
// if there's data left over, flag as ready to read again
|
||||||
|
// otherwise... flag as ready to write?
|
||||||
|
if len(b) > 0 {
|
||||||
|
s.dataReady <- struct{}{}
|
||||||
|
} else {
|
||||||
|
//p.writeReady <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note a read error should not be possible here
|
||||||
|
// as all traffic (including errors) can be wrapped
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close (TODO) should politely close all connections, if possible (set Read() to io.EOF, or use ErrClosed?)
|
||||||
|
func (s *Server) Close() error {
|
||||||
|
return errors.New("not implemented")
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
package packer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HeaderLengthState State = 1 + iota
|
||||||
|
HeaderState
|
||||||
|
PayloadState
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FamilyIndex int = iota
|
||||||
|
AddressIndex
|
||||||
|
PortIndex
|
||||||
|
LengthIndex
|
||||||
|
ServiceIndex
|
||||||
|
)
|
||||||
|
|
||||||
|
type Header struct {
|
||||||
|
Family string
|
||||||
|
Address string
|
||||||
|
Port string
|
||||||
|
Service string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) unpackV1(b []byte) error {
|
||||||
|
z := 0
|
||||||
|
for {
|
||||||
|
if z > 10 {
|
||||||
|
panic("stuck in an infinite loop?")
|
||||||
|
}
|
||||||
|
z += 1
|
||||||
|
n := len(b)
|
||||||
|
// at least one loop
|
||||||
|
if z > 1 && n < 1 {
|
||||||
|
fmt.Println("v1 end", z, n)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
switch p.parseState {
|
||||||
|
case HeaderLengthState:
|
||||||
|
fmt.Println("v1 h len")
|
||||||
|
b = p.unpackV1HeaderLength(b)
|
||||||
|
case HeaderState:
|
||||||
|
fmt.Println("v1 header")
|
||||||
|
b, err = p.unpackV1Header(b, n)
|
||||||
|
if nil != err {
|
||||||
|
fmt.Println("v1 header err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case PayloadState:
|
||||||
|
fmt.Println("v1 payload")
|
||||||
|
// if this payload is complete, reset all state
|
||||||
|
if p.state.payloadWritten == p.state.payloadLen {
|
||||||
|
p.state = ParserState{}
|
||||||
|
}
|
||||||
|
b, err = p.unpackV1Payload(b, n)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// do nothing
|
||||||
|
return errors.New("error unpacking")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) unpackV1HeaderLength(b []byte) []byte {
|
||||||
|
p.state.headerLen = int(b[0])
|
||||||
|
fmt.Println("unpacked header len", p.state.headerLen)
|
||||||
|
b = b[1:]
|
||||||
|
p.state.written += 1
|
||||||
|
p.parseState += 1
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) unpackV1Header(b []byte, n int) ([]byte, error) {
|
||||||
|
fmt.Println("got", len(b), "bytes", string(b))
|
||||||
|
m := len(p.state.header)
|
||||||
|
k := p.state.headerLen - m
|
||||||
|
if n < k {
|
||||||
|
k = n
|
||||||
|
}
|
||||||
|
p.state.written += k
|
||||||
|
c := b[0:k]
|
||||||
|
b = b[k:]
|
||||||
|
fmt.Println("has", m, "want", k, "more and have", len(b), "more")
|
||||||
|
p.state.header = append(p.state.header, c...)
|
||||||
|
if p.state.headerLen != len(p.state.header) {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
parts := strings.Split(string(p.state.header), ",")
|
||||||
|
p.state.header = nil
|
||||||
|
if len(parts) < 5 {
|
||||||
|
return nil, errors.New("error unpacking header")
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadLenStr := parts[LengthIndex]
|
||||||
|
payloadLen, err := strconv.Atoi(payloadLenStr)
|
||||||
|
if nil != err {
|
||||||
|
return nil, errors.New("error unpacking header payload length")
|
||||||
|
}
|
||||||
|
p.state.payloadLen = payloadLen
|
||||||
|
port, _ := strconv.Atoi(parts[PortIndex])
|
||||||
|
service := parts[ServiceIndex]
|
||||||
|
|
||||||
|
if "control" == service {
|
||||||
|
return nil, errors.New("'control' messages not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := Addr{
|
||||||
|
family: parts[FamilyIndex],
|
||||||
|
addr: parts[AddressIndex],
|
||||||
|
port: port,
|
||||||
|
scheme: Scheme(service),
|
||||||
|
}
|
||||||
|
p.state.addr = addr
|
||||||
|
/*
|
||||||
|
p.state.conn = p.conns[addr.Network()]
|
||||||
|
if nil == p.state.conn {
|
||||||
|
rconn, wconn := net.Pipe()
|
||||||
|
conn := Conn{
|
||||||
|
updated: time.Now(),
|
||||||
|
relayRemoteAddr: addr,
|
||||||
|
relay: rconn,
|
||||||
|
local: wconn,
|
||||||
|
}
|
||||||
|
copied := conn
|
||||||
|
p.state.conn = &copied
|
||||||
|
p.conns[addr.Network()] = p.state.conn
|
||||||
|
p.newConns <- p.state.conn
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
p.parseState += 1
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) unpackV1Payload(b []byte, n int) ([]byte, error) {
|
||||||
|
// Handle "connect" and "end"
|
||||||
|
if 0 == p.state.payloadLen {
|
||||||
|
/*
|
||||||
|
p.newMsg <- msg{
|
||||||
|
addr: Addr,
|
||||||
|
bytes: []byte{},
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := &p.state.conn.relayRemoteAddr
|
||||||
|
if "end" == string(addr.scheme) {
|
||||||
|
if err := p.state.conn.Close(); nil != err {
|
||||||
|
// TODO log potential error?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
*/
|
||||||
|
|
||||||
|
p.handler.WriteMessage(p.state.addr, []byte{})
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
k := p.state.payloadLen - p.state.payloadWritten
|
||||||
|
if k < n {
|
||||||
|
k = n
|
||||||
|
}
|
||||||
|
c := b[0:k]
|
||||||
|
b = b[k:]
|
||||||
|
// TODO don't let a write on one connection block others,
|
||||||
|
// and also put backpressure on just that connection
|
||||||
|
/*
|
||||||
|
m, err := p.state.conn.local.Write(c)
|
||||||
|
p.state.written += m
|
||||||
|
p.state.payloadWritten += m
|
||||||
|
if nil != err {
|
||||||
|
// TODO we want to surface this error somewhere, but not to the websocket
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
p.handler.WriteMessage(p.state.addr, c)
|
||||||
|
p.state.written += k
|
||||||
|
p.state.payloadWritten += k
|
||||||
|
p.written = p.state.written
|
||||||
|
|
||||||
|
// if this payload is complete, reset all state
|
||||||
|
if p.state.payloadWritten == p.state.payloadLen {
|
||||||
|
p.state = ParserState{}
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package mplexer
|
package mplexer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"git.coolaj86.com/coolaj86/go-telebitd/mplexer/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SortingHat interface {
|
type SortingHat interface {
|
||||||
LookupTarget(*Addr) (net.Conn, error)
|
LookupTarget(*packer.Addr) (*packer.Conn, error)
|
||||||
Authz() (string, error)
|
Authz() (string, error)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue