Skip to main content

Wisp

Wisp Specifications

GitHub

What is Wisp?

Wisp is designed to be a low-overhead, easy to implement protocol for proxying multiple TCP and UDP sockets over a single websocket connection.

What does this have to do with proxies?

Prior to the creation of Wisp, proxy traffic over backends such as the Bare Server or Rammerhead are sent completely unencrypted to the server host. This may work fine if you are hosting your own proxy, but when using public links you obtain from a discord server the risk of the host stealing your tokens from proxy usage might be a problem.

Since Wisp can proxy all TCP traffic, transports like Epoxy or Libcurl will encrypt your traffic with TLS, making even the server host unable to access any data besides the hostnames of the sites you visit. Wisp's UDP feature also allows for use as a highly censorship-resistant VPN with Whisper.

Server Implementations:

Client Implementations:

Software Using Wisp:

  • libcurl.js - A port of libcurl to WebAssembly, for proxying HTTPS requests from the browser with full TLS encryption
  • epoxy-tls - An encrypted proxy for browser javascript, written in Rust.
  • Ultraviolet - A sophisticated web proxy, which uses either libcurl.js or epoxy.
  • Whisper - A client for Wisp that exposes the connection over a TUN device.

Packet format

Field NameField TypeNotes
Packet Typeuint8_tThe packet type, covered in the next section.
Stream IDuint32_tRandom stream ID assigned by the client.
Payloadchar[]Payload takes up the rest of the packet.

Every packet must follow this format regardless of the type. Note that all data types are little-endian.

Packet Types

Each packet type has a different format for the payload, which is detailed below.

0x01 - CONNECT

Payload Format

Field NameField TypeNotes
Stream Typeuint8_tWhether the new stream should use a TCP or UDP socket.
Destination Portuint16_tDestination TCP/UDP port for the new stream.
Destination Hostnamechar[]Destination hostname, in a UTF-8 string.

Behavior

The client needs to send a CONNECT packet to the server to create a new stream under the same websocket. The stream ID chosen by the client at this point will be associated with this stream for all future messages. When the server receives this packet, it must validate this information, and if the payload is invalid, a CLOSE packet must be sent.

Once the payload has been validated, the server must immediately try to establish a TCP/UDP socket to the specified hostname and port. If this fails, the server must send a CLOSE packet with the reason. To reduce overall delay, the client can begin sending data before the any CONTINUE packet has been received from the server.

The stream type field determines whether the connection uses TCP or UDP. 0x01 in this field means TCP, and 0x02 means UDP. UDP support is optional for both the server and the client.

0x02 - DATA

Payload Format

Field NameField TypeNotes
Stream Payloadchar[]The data which is sent to and from the destination server.

Behavior

Any DATA packets sent from the client to the server must be proxied to the TCP/UDP socket associated with the stream ID of the packet. On the server, the received payload must be buffered before being sent to the TCP/UDP socket in order to handle congestion. The size of this send buffer is predetermined and must be the same for every stream.

Any DATA packets sent from the server to the client must be interpreted as coming from the TCP/UDP socket associated with the stream ID of the packet.

For TCP streams, the server must buffer any packets received from the client in a FIFO queue, and it must keep a separate buffer for each TCP stream.

0x03 - CONTINUE

Payload Format

Field NameField TypeNotes
Buffer Remaininguint32_tThe number of packets that the server can buffer for the current stream.

Behavior

If the associated stream is a UDP socket, then CONTINUE packets must not be sent, and the client does not keep track of any buffer for the stream.

When the client receives a CONTINUE packet from the server, it must store the received buffer size. When sending a DATA packet, this value should be decremented by 1 on the client. Once the remaining buffer size reaches zero, the client cannot send any more DATA packets, until it receives another CONTINUE packet which resets this value.

The server must send another CONTINUE packet when it has received the same number of packets from the client as its own maximum buffer size. The server should regularly send CONTINUE packets before this point to ensure minimal delays when receiving data from the client.

0x04 - CLOSE

Payload format

Field NameField TypeNotes
Close Reasonuint8_tThe reason for closing the connection.

Behavior

Any CLOSE packets sent from either the server or the client must immediately close the associated stream and TCP socket. The close reason in the payload doesn't affect this behavior, but may provide extra information which is useful for debugging.

Client/Server Close Reasons

  • 0x01 - Reason unspecified or unknown. Returning a more specific reason should be preferred.
  • 0x02 - Voluntary stream closure, which would equate to one side resetting the connection.
  • 0x03 - Unexpected stream closure due to a network error.

Server Only Close Reasons

  • 0x41 - Stream creation failed due to invalid information. This could be sent if the destination was a reserved address or the port is invalid.
  • 0x42 - Stream creation failed due to an unreachable destination host. This could be sent if the destination is an domain which does not resolve to anything.
  • 0x43 - Stream creation timed out due to the destination server not responding.
  • 0x44 - Stream creation failed due to the destination server refusing the connection.
  • 0x47 - TCP data transfer timed out.
  • 0x48 - Stream destination address/domain is intentionally blocked by the proxy server.
  • 0x49 - Connection throttled by the server.

Client Only Close Reasons

  • 0x81 - The client has encountered an unexpected error and is unable to receive any more data.

HTTP Behavior

Since the entire protocol takes place over websockets, a few rules need to be in place to ensure that an HTTP connection can be upgraded successfully.

Server Architecture

The server must consist of a HTTP and websocket server that conforms to the respective standards.

Choosing a Websocket URL

To ensure compatibility with previous wsproxy implementations, the URL of the websocket should always end with a trailing forward slash (/) to prevent confusion with wsproxy endpoints.

For example, a wsproxy endpoint might look like this: ws://example.com/customprefix/host:port

Meanwhile a Wisp endpoint would look like this: ws://example.com/customprefix/

It is up to the server implementation to interpret the prefix. It may be ignored, or used for gatekeeping in order to establish password-based authentication.

Establishing a Websocket Connection

The client must establish the connection by performing a standard websocket handshake. The Sec-WebSocket-Protocol header does not need to be set.

Immediately after a websocket connection is established, the server must send a CONTINUE packet containing the initial buffer size for each stream. The stream ID for this packet must be set to 0, which corresponds to the version of the Wisp protocol (0 means Wisp v1). The client must wait for this CONTINUE packet to be received before beginning any communications. The purpose of this packet is to ensure that the client does not have to wait for a CONTINUE packet for the creation of each stream, reducing the overall delay.

The Wisp protocol specifications were written by @ading2210.