httptun

package module
v0.0.0-...-00e4402 Latest Latest
Warning

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

Go to latest
Published: Nov 23, 2023 License: Apache-2.0 Imports: 14 Imported by: 0

README

httptun

Tunnel TCP over a WebSocket connection. We use this with Google's Identity-Aware Proxy to get an authenticated TCP connection.

To use it, start the server somewhere and then use NewClient() in your client code. Use (*Client).Dial() to get a TCP connection in the form of a net.Conn.

# Listen for Websocket connections on, ie. all network interfaces.
export HTTPTUN_LISTEN_ADDR=:80
# Proxy the tunneled TCP connections to some upstream.
export HTTPTUN_DST_ADDR=1.2.3.4:5678
# Start the server.
go run ./cmd/server
# There's a basic healthcheck at /health
curl localhost:80/health
#=> ok
# Establish a connection at /ws with a client constructed with NewClient().

License

All the code within this repository is licensed under Apache-2.0.

Copyright 2023 Cockroach Labs, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Documentation

Overview

Package httptun implements the client and server for the TCP over HTTP tunnel.

Index

Constants

View Source
const (
	CodeNoError = ErrorCode(iota)
	CodeSessionNotFound
	CodeCannotResume
	CodeDialError
)

List of error codes.

Variables

View Source
var ErrPreempted = errors.New("buffer: preempted")

ErrPreempted (preemption) is a mechanism that makes Read calls of buffers return immediately, and notify the caller they have been preempted. The caller should immediately exit as it indicates that their flow is no longer valid.

Functions

This section is empty.

Types

type Buffer

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

Buffer provides the flexibility to recover from an interrupted connection. Each part of a working connection is referred to as a "flow". When a flow is resumed with (*Flow).Resume, a handshake is performed that adjusts the (*Buffer).readerPos on the other side to the appropriate position to continue the TCP connection based on the number of bytes last successfully received. Thus the job of the buffer is to provide enough backwards space for the readerPos to jump back to. This is represented by (*Buffer).minBehindBuffer. The forward space of the buffer isn't technically necessary, it's there to improve throughput and to keep connections from stalling when a connection is interrupted.

The journey of a "packet" looks like this:

1. The underlying connection is read by the caller, or inside server.go 2. (*Buffer).Write() is called, which appends the data to the buffer. 3. (*Buffer).Read() is called, which reads from the buffer from (*Buffer).readerPos. 4. (net.Conn).Write() is called on the "unreliable" connection. --- You're now on the other side --- 5. (net.Conn).Read() is called on the "unreliable" connection from within (*Flow).Resume. 6. Inside (*Flow).Resume, the read data is written to a small, simple buffer (*Stream).writeBuffer. 7. (*Stream).Read() is called, which reads from (*Stream).writeBuffer and provides the "reliable" connection.

The steady state of the Buffer looks like this:

                          maxTotalBuffer (combined buffer)
                  <----------------------------------------------->

minBehindBuffer (buffer for recovering a lost connection)
                  <----------------->

                  bufferHead      readerPos (position in buffer consumer is reading from)
                  v                  v

(*Buffer).buffer = []byte{.................................................}

func NewBuffer

func NewBuffer(maxTotal, minBehind int64, logger *zap.SugaredLogger) *Buffer

NewBuffer constructs a new buffer with the given size and logger. See Buffer for more information.

func (*Buffer) Close

func (b *Buffer) Close()

Close closes the buffer. It will cause all readers and writers to return with an error.

func (*Buffer) CloseWithErr

func (b *Buffer) CloseWithErr(err error)

CloseWithErr closes the buffer with a specific error. It will cause all readers and writers to return with the specified error.

func (*Buffer) Read

func (b *Buffer) Read(flowID uuid.UUID, p []byte) (n int, err error)

Read reads from the buffer. It will block until there is data to read. ErrPreempted is returned if the flow is preempted by another flow.

func (*Buffer) SetReaderPos

func (b *Buffer) SetReaderPos(pos int64) error

SetReaderPos sets the reader position of the buffer, it used during the handshake of httptun to resume a TCP connection.

func (*Buffer) TakeReader

func (b *Buffer) TakeReader(flowID uuid.UUID) *BufferReader

TakeReader returns a wrapped io.Reader from the *Buffer that is tied to a specific flow ID.

func (*Buffer) Write

func (b *Buffer) Write(p []byte) (n int, err error)

Write writes to the buffer. It will block until there is space to write.

func (*Buffer) WriterPos

func (b *Buffer) WriterPos() int64

WriterPos returns the current position of the writer in the buffer.

type BufferReader

type BufferReader struct {
	*Buffer
	// contains filtered or unexported fields
}

BufferReader is a reader that reads from a Buffer. It wraps Buffer with a flow ID that is used to manage the flows being read from the buffer.

func (*BufferReader) Read

func (b *BufferReader) Read(p []byte) (n int, err error)

Read implements io.Reader. It will block until there is data to read. ErrPreempted is returned if the flow is preempted by another flow.

type Client

type Client struct {
	*websocket.Dialer
	Addr           string
	RequestHeaders func() http.Header
	Logger         *zap.SugaredLogger
	// KeepAlive sets the interval and timeout (2*KeepAlive) of WebSocket keep alive messages.
	// If unset, defaults to 5 seconds.
	KeepAlive time.Duration
}

Client opens Websocket connections, returning them as a net.Conn. A zero Client is ready for use without initialization. If Dialer is nil, websocket.DefaultDialer is used.

func (*Client) Dial

func (c *Client) Dial(ctx context.Context) (net.Conn, error)

Dial forms a tunnel to the backend TCP endpoint and returns a net.Conn.

Callers are responsible for closing the returned connection.

type ErrorCode

type ErrorCode int64

ErrorCode represents an error code in a handshake.

type Flow

type Flow struct {
	*Stream
	// contains filtered or unexported fields
}

Flow is an active "instance" of a stream, which represents an unreliable connection such as a WebSocket connection.

func (*Flow) Close

func (f *Flow) Close()

Close causes a panic to prevent misuse. Closing the flow is prohibited but *Flow wraps a *Stream, so the Close method is implemented to prevent accidental misuse of the (*Stream).Close method.

func (*Flow) IsValid

func (f *Flow) IsValid() bool

IsValid returns true if the flow is still valid (i.e. the stream's current flow ID is this flow's, and there is no underlying error reported.

func (*Flow) Resume

func (f *Flow) Resume(unreliable net.Conn, resumeFrom int64) error

Resume attempts to resume the flow's stream with the given "unreliable" connection (typically a WebSocket connection) and resumeFrom value from the handshake. Once the flow is successfully resumed, Resume returns. Call Wait to wait for the flow to finish. unreliable is automatically closed.

func (*Flow) Wait

func (f *Flow) Wait()

Wait waits for the flow to end, you would typically call this after a successful call to Resume.

type Handshake

type Handshake struct {
	ID         uuid.UUID
	ResumeFrom int64
	ErrorCode  ErrorCode
}

Handshake is the handshake message sent by both the client and server to negotiate connection resumption.

type Server

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

Server implements http.Handler for the server (termination point) of a TCP over HTTP tunnel.

func NewServer

func NewServer(dst string, timeout time.Duration, logger *zap.SugaredLogger) *Server

NewServer creates a new server that can be used to serve HTTP requests over a websocket connection. timeout specifies the maximum time that a stream can be idle with no active flows before it is closed.

func (*Server) Close

func (s *Server) Close()

Close shuts down the server, closing existing streams and rejects new connections.

func (*Server) OnStreamClose

func (s *Server) OnStreamClose(f func(streamID uuid.UUID, startTime time.Time, bytesRead, bytesWritten int64))

OnStreamClose sets a callback handler for when a stream closes. The callback should never block as it is not called in a separate goroutine.

func (*Server) ServeHTTP

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

ServeHTTP implements the http.Handler interface.

type Stream

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

Stream is a resumable stream, it represents a logical, persistent (extra-reliable) connection.

func NewStream

func NewStream(maxTotal, minBehind int64, logger *zap.SugaredLogger) *Stream

NewStream creates a new stream.

func (*Stream) Close

func (s *Stream) Close() error

Close closes the stream, immediately preempting all Read, Write calls and flows. It is safe to call Close multiple times.

func (*Stream) IsClosed

func (s *Stream) IsClosed() bool

IsClosed returns true if the stream is closed.

func (*Stream) LocalAddr

func (s *Stream) LocalAddr() net.Addr

LocalAddr implements net.Conn. It returns a dummy but valid net.Addr value.

func (*Stream) NewFlow

func (s *Stream) NewFlow() (*Flow, int64, error)

NewFlow prepares the stream for resumption with a new flow. It preempts any existing flows and returns the ResumeFrom value of the stream that should be given to the other side in a handshake.

func (*Stream) OnClose

func (s *Stream) OnClose(f func(startTime time.Time, bytesRead, bytesWritten int64))

OnClose sets a callback function that is called when a stream closes. It provides the start time of the connection, and the total number of bytes read from and written to the stream.

func (*Stream) Read

func (s *Stream) Read(p []byte) (n int, err error)

Read implements io.Reader. Stream's Read method is behind a buffer and is interruption-free when flows are interrupted.

func (*Stream) RemoteAddr

func (s *Stream) RemoteAddr() net.Addr

RemoteAddr implements net.Conn. It returns a dummy but valid net.Addr value.

func (*Stream) SetDeadline

func (s *Stream) SetDeadline(t time.Time) error

SetDeadline implements net.Conn. It is an unimplemented no-op.

func (*Stream) SetReadDeadline

func (s *Stream) SetReadDeadline(t time.Time) error

SetReadDeadline implements net.Conn. It is an unimplemented no-op.

func (*Stream) SetWriteDeadline

func (s *Stream) SetWriteDeadline(t time.Time) error

SetWriteDeadline implements net.Conn. It is an unimplemented no-op.

func (*Stream) Write

func (s *Stream) Write(p []byte) (n int, err error)

Write implements io.Writer. Stream's Write method writes into the *Buffer (see the godoc on *Buffer for more details) and is interruption-free when flows are interrupted.

type WebsocketConn

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

WebsocketConn wraps *websocket.Conn into implementing net.Conn

func NewWebsocketConn

func NewWebsocketConn(conn *websocket.Conn, writeMu *sync.Mutex, waitClose func()) *WebsocketConn

NewWebsocketConn creates a new WebsocketConn from an open websocket connection which implements net.Conn

func (*WebsocketConn) Close

func (c *WebsocketConn) Close() error

Close implements net.Conn

func (*WebsocketConn) LocalAddr

func (c *WebsocketConn) LocalAddr() net.Addr

LocalAddr implements net.Conn

func (*WebsocketConn) Read

func (c *WebsocketConn) Read(b []byte) (int, error)

Read implements net.Conn

func (*WebsocketConn) RemoteAddr

func (c *WebsocketConn) RemoteAddr() net.Addr

RemoteAddr implements net.Conn

func (*WebsocketConn) SetDeadline

func (c *WebsocketConn) SetDeadline(t time.Time) error

SetDeadline implements net.Conn

func (*WebsocketConn) SetReadDeadline

func (c *WebsocketConn) SetReadDeadline(t time.Time) error

SetReadDeadline implements net.Conn

func (*WebsocketConn) SetWriteDeadline

func (c *WebsocketConn) SetWriteDeadline(t time.Time) error

SetWriteDeadline implements net.Conn

func (*WebsocketConn) Write

func (c *WebsocketConn) Write(b []byte) (int, error)

Write implements net.Conn

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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