wsplice

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 1, 2017 License: MIT Imports: 18 Imported by: 0

README

wsplice Build Status

wsplice is a websocket multiplexer, allowing you to connect to multiple remote hosts though a single websocket.

Usage

Download a binary from the Releases page. You can then run it from the command line. wsplice can be configured to use client certification authentication, and/or an allowlist of remote hostnames it's allowed to connect to.

# Run a publicly accessible instance with client cert auth
./wsplice --tls-cert=my-cert.pem \
    --tls-key=my-key.pem \
    --tls-ca=my-ca.pem \
    --listen=0.0.0.0:3000

# Omit the CA cert to run it over TLS, and allow it to connect
# to example.com and ws.example.com
./wsplice --tls-cert=my-cert.pem \
    --tls-key=my-key.pem \
    --allowed-hostnames="example.com ws.example.com"
Protocol

Websocket frames are prefixed with two bytes, as a big endian uint16, to describe who that message goes to. The magic control index is [0xff, 0xff], which is a simple JSON RPC protocol. To connect to another server, you might do something like this in Node.js:

const payload = Buffer.concat([
    Buffer.from([0xff, 0xff]),
    Buffer.from(JSON.stringify({
        id: 42,
        type: "method",
        method: "connect",
        params: {
            url: "ws://example.com",
            headers: { /* ... */ }, // optional
            subprotocols: [/* ... */], // optional
        }
    }))
]);

websocket.write(payload);

The response is a JSON object like:

{
  "id": 42,
  "type": "reply",
  "result": {
    "index": 0
  }
}

In this case the socket index is 0. You can send messages to that websocket by prefixing the messages with 0, encoded as a big endian uint16, and likewise wsplice will proxy and prefix messages that it gets from that server with the same. All frames, with the exception of ping and pong frames (which are handled automatically for you) will be proxied.

Once the client disconnects, the wsplice will call onSocketDisconnect. For example:

{
  "id": 0,
  "type": "method",
  "method": "onSocketDisconnect",
  "params": {
    "code": 4123,
    "message": "The socket close reason, if any",
    "index": 0
  }
}
Performance

wsplice spends most time (upwards of 90%) handling network reads/writes; performance is generally bounded by how much data your operating system's kernel and send or receive from a single connection.

Throughput payload=32B payload=128B payload=1024B payload=4098B
clients=32 231 mbps 235 mbps 2940 mbps 11200 mbps
clients=128 327 mbps 333 mbps 2520 mbps 14600 mbps
clients=512 46.3 mbps 533 mbps 3570 mbps 11200 mbps
Latency
clients=32 5.6μs 4.6μs 5.4μs 7.4μs
clients=128 4.7μs 5.2μs 5.7μs 5.7μs
clients=512 3.7μs 4.0μs 5.2μs 7.1μs

These measurements were taken on an B8 Azure VM running Ubuntu 16.04, using the binary in ./cmd/bench.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Dispose

func Dispose(v interface{})

Dispose calls a Disposable's Dispose() method, if the provided interface implements Disposable.

Types

type Config

type Config struct {
	FrameSizeLimit int64

	WriteTimeout time.Duration
	ReadTimeout  time.Duration
	DialTimeout  time.Duration

	HostnameAllowlist []string
}

type ConnectCommand

type ConnectCommand struct {
	URL          string            `json:"url"`
	Headers      map[string]string `json:"headers"`
	Subprotocols []string          `json:"subprotocols"`
	Timeout      int               `json:"timeout"`
}

ConnectCommand is sent on the controls socket when the client wants to initiate a new connection to a remote server.

type ConnectResponse

type ConnectResponse struct {
	Index int `json:"index"` // newly-allocated index to use to reference this socket
}

ConnectResponse is sent back in response to a ConnectCommand

type Connection

type Connection struct {
	// contains filtered or unexported fields
}

func (*Connection) Close

func (c *Connection) Close(frame ws.Frame)

func (*Connection) Start

func (c *Connection) Start()

Start begins reading data from the connection, sending it to the Session.

type ConnectionTarget

type ConnectionTarget struct {
	// contains filtered or unexported fields
}

ConnectionTarget is a target to write out the data to an external connection.

func (*ConnectionTarget) Close

func (c *ConnectionTarget) Close()

Close implements Target.Close.

func (*ConnectionTarget) Pull

func (c *ConnectionTarget) Pull(header ws.Header, _ *Socket, frame *io.LimitedReader) (err error)

Pull implements Target.Pull. It copies the frame to the target connection.

type Disposable

type Disposable interface {
	Dispose()
}

Disposable is an interface that describes something that can optionally be disposed of after it's no longer needed.

type ErrorCode

type ErrorCode uint
const (
	BadJSON ErrorCode = 4000 + iota
	FrameTooShort
	FrameTooLong
	UnknownMethod
	UnknownConnection
	InvalidURL
	InvalidHostname
	DialError
)

func (ErrorCode) Error

func (e ErrorCode) Error() string

func (ErrorCode) ResponseError

func (e ErrorCode) ResponseError() *ResponseError

func (ErrorCode) WithPath

func (e ErrorCode) WithPath(path ...string) *ResponseError

type FragmentCollector

type FragmentCollector struct {
	// contains filtered or unexported fields
}

FragmentCollector is a structure that collects multiple fragmented websocket frames together into a single reader.

func NewFragmentCollector

func NewFragmentCollector(header ws.Header, s *Socket) FragmentCollector

NewFragmentCollector creates a collector on the socket. It assumes that the first header of a fragmented has just been read.

func (*FragmentCollector) Collect

func (f *FragmentCollector) Collect(maxSize int64) (header ws.Header, r io.Reader, err error)

Collect returns a reader for the fragmented websocket frame.

type MaskedReader

type MaskedReader struct {
	*io.LimitedReader
	// contains filtered or unexported fields
}

MaskedReader wraps an io.Reader and deciphers the content.

func NewMasked

func NewMasked(r *io.LimitedReader, offset int, mask [4]byte) *MaskedReader

func (MaskedReader) Read

func (m MaskedReader) Read(p []byte) (n int, err error)

Read implements io.Read

type Method

type Method struct {
	ID     int             `json:"id"`
	Type   string          `json:"type"`
	Method string          `json:"method"`
	Params json.RawMessage `json:"params"`
}

Method is a generic RPC method call.

type RPC

type RPC struct {
	// contains filtered or unexported fields
}

RPC implements a simple bidirectional RPC server.

func (*RPC) Dispatch

func (r *RPC) Dispatch(method Method) (v interface{}, err error)

Dispatch sends a method call to the correct handler, return the packet to reply with, or an error.

func (*RPC) ReadMethodCall

func (r *RPC) ReadMethodCall(sr io.Reader) (Method, error)

ReadMethodCall reads a Method off the reader, returning an error or the unmarshaled method call.

type RPCTarget

type RPCTarget struct {
	// contains filtered or unexported fields
}

RPCTarget is a target for an RPC call.

func NewRPCTarget

func NewRPCTarget(copyBuffer []byte, s *Session) *RPCTarget

NewRPCTarget creates and returns a new target for the RPC call.

func (*RPCTarget) Close

func (r *RPCTarget) Close()

Close implements Target.Close.

func (*RPCTarget) Pull

func (r *RPCTarget) Pull(header ws.Header, socket *Socket, frame *io.LimitedReader) (err error)

Pull implements Target.Pull. It pipes the RPC call from the socket to the RPC goroutine (kicked off in NewRPCTarget)

type Reply

type Reply struct {
	ID     int            `json:"id"`
	Type   string         `json:"type"`
	Result interface{}    `json:"result,omitempty"`
	Error  *ResponseError `json:"error,omitempty"`
}

Reply is a generic RPC reply.

type ResponseError

type ResponseError struct {
	Code    ErrorCode `json:"code"`
	Message string    `json:"message"`
	Path    string    `json:"path,omitempty"`
}

A ResponseError can be included in method replies.

func (ResponseError) Error

func (r ResponseError) Error() string

type Server

type Server struct {
	Config *Config
}

func (*Server) ServeConn

func (s *Server) ServeConn(conn net.Conn)

func (*Server) ServeHTTP

func (s *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request)

type Session

type Session struct {
	Socket
	// contains filtered or unexported fields
}

func (*Session) Close

func (s *Session) Close()

func (*Session) GetConnection

func (s *Session) GetConnection(index int) *Connection

GetConnection returns the connection at the index, or nil.

func (*Session) RemoveConnection

func (s *Session) RemoveConnection(index int)

func (*Session) SendControlFrame

func (s *Session) SendControlFrame(v interface{})

SendControlFrame pushes a method to the socket, prefixing it with the control index.

func (*Session) SendMethod

func (s *Session) SendMethod(name string, params interface{})

SendMethod dispatches a method call to the client, and returns without waiting for a response.

func (*Session) Start

func (s *Session) Start()

type Socket

type Socket struct {
	Conn   net.Conn
	Reader *bufio.Reader
	// contains filtered or unexported fields
}

Socket is a wrapper that provides useful utilities around a websocket net.Conn.

func NewSocket

func NewSocket(conn net.Conn, config *Config) *Socket

NewSocket creates a new websocket.

func (*Socket) Close

func (s *Socket) Close() error

Close closes the underlying connection.

func (*Socket) CopyData

func (s *Socket) CopyData(header ws.Header, r io.Reader) error

CopyIndexedData copies data from the CountingReader to the socket.

func (*Socket) CopyIndexedData

func (s *Socket) CopyIndexedData(index int, header ws.Header, r io.Reader) (err error)

CopyIndexedData copies data from the CountingReader to the socket, prefixing it with the index for the incoming socket.

func (*Socket) ReadNextFrame

func (s *Socket) ReadNextFrame() (header ws.Header, err error)

ReadNextFrame reads the next non-control or close frame off of the socket. Ping/pong frames are handled automatically.

func (*Socket) ReadNextWithBody

func (s *Socket) ReadNextWithBody() (header ws.Header, r io.Reader, err error)

ReadNextWithBody returns the next non-control or close frame off the socket, joining fragmented messages bodies. This should only be used if you actually need to join fragmented messages, as it buffers data internally in memory.

func (*Socket) WriteData

func (s *Socket) WriteData(header ws.Header, b []byte) error

CopyIndexedData copies data from the CountingReader to the socket.

func (*Socket) WriteFrame

func (s *Socket) WriteFrame(frame ws.Frame) error

WriteFrame writes a frame to the websocket.

func (*Socket) WriteIndexedData

func (s *Socket) WriteIndexedData(index int, header ws.Header, b []byte) (err error)

CopyIndexedData writes data from the byte slice to the socket, prefixing it with the index for the incoming socket.

type SocketClosedCommand

type SocketClosedCommand struct {
	Index  int    `json:"index"`
	Code   int    `json:"code"`
	Reason string `json:"reason"`
}

type Target

type Target interface {
	Pull(header ws.Header, socket *Socket, frame *io.LimitedReader) (err error)
	Close()
}

Target is an "action" that the session takes. When the Session reads the start of a new frame/operation, it decides what target that hits and sets it internally. PushFrame can be called many times (for fragmented messages).

type TerminateCommand

type TerminateCommand struct {
	Index  int    `json:"index"`
	Code   int    `json:"code"`
	Reason string `json:"reason"`
}

A TerminateCommand is sent to signalClosed a socket, by its index.

type TerminateResponse

type TerminateResponse struct {
}

A TerminateResponse is sent in response to a TerminateCommand.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL