Standard library · Network

net.transport

The byte stream under the VPN protocols: TCP, TLS, WebSocket and HTTP upgrade connections with SNI, ALPN and ClientHello fragmentation, plus byte buffers and addresses.

import net.transport

A proxy protocol such as VLESS or VMess says what the first bytes of a connection mean. It does not say how the connection itself is made, and that is the part a censor looks at: is it plain TCP, TLS to which name, a WebSocket on which path, under which Host header. net.transport is that layer. It turns "connect to this host and port with these settings" into a Conn that is read and written the same way whatever is underneath.

The package has four jobs:

  • Carriers. Dial and Listen work over plain TCP, TLS, WebSocket (alone or inside TLS) and a bare HTTP upgrade. FromStream and AsStream let any net.tunnel stream carry a protocol too, so a protocol can run inside another one.
  • Looking like something else. The TLS server name (sni) and the HTTP Host header are separate settings, so a client can connect to a CDN front name while asking for another site. The WebSocket path works as a shared secret: a request for any other path gets a fake nginx 404 page.
  • ClientHello fragmentation. fragment sends the first bytes of the TLS handshake in small pieces with a pause between them, which defeats some filters that read the server name from the first packet.
  • Shared plumbing. Bytes is the growable buffer the protocol packages build headers in, and Addr is a target address that knows its wire form.

Which carrier a Settings value selects

Every dial and listen call takes a Settings value. Its zero value is plain TCP.

SettingsCarrier
network = "tcp" (default)The raw stream, with TLS on top when tls = true.
network = "ws"An HTTP/1.1 request that upgrades to a WebSocket, then binary frames. Client frames are masked, as the standard requires. With tls = true this is wss.
network = "httpupgrade"The same upgrade request without WebSocket framing: after the 101 answer the bytes flow raw. It is cheaper than ws and looks the same to a proxy that only reads the first request.
tls = trueThe client checks the server certificate against the system roots or ca_file, using sni (or the dialed host when it is empty) as the name. The server needs cert_file and key_file.

Any other network text is not rejected here: the connection is simply plain. The link parsers of the protocol packages refuse unsupported types before they get this far.

What trips people up

  • Accept hands each new connection to a worker. TLS and the WebSocket upgrade run in a worker with a time limit (handshake_timeout_ms, 10 seconds) and at most max_handshakes (128) run at once; a client that stalls is dropped and a connection over the limit is closed at once. Accept then returns the next finished connection, or a closed one whose err says why a handshake failed. AcceptRaw and Upgrade still exist for a loop of your own.
  • sni is not host. sni is the name in the TLS handshake and the name the certificate is checked against; host is the Host header inside the upgrade request. Behind a CDN they usually differ.
  • The ALPN setting is one string. Offer "h2" or "http/1.1", not a list. Over WebSocket use "http/1.1", because the upgrade is an HTTP/1.1 feature.
  • fingerprint changes nothing. The field is kept so links that carry fp=chrome round-trip, but the TLS handshake is this library's own, not a copy of a browser's. A filter that recognizes the handshake shape is not fooled.
  • timeout_ms covers the connect only. For an established connection set read_timeout_ms and write_timeout_ms: a read or write that waits that long ends with 0 or less. They are idle limits, so leave them 0 for a tunnel that may sit quiet.
  • Buffers are manual. NewBytes allocates and FreeBytes frees; nothing collects them.
  • No XHTTP, QUIC or plain HTTP/2 transport. gRPC (network = "grpc"), Reality (security = "reality") and Mux.Cool are here; see the sections below.

Buffers and addresses

A Bytes value is a pointer, a length and a capacity. The Put functions append (and grow it), At and Peek16 read back, and ToHex shows the content. A target address is a Addr: NewAddr looks at the text and decides between an IPv4 address, an IPv6 address and a domain name, and PutAddr writes it in the order the VLESS and VMess headers use (port, type, address). ParseUUID turns the usual 36 character text into the 16 bytes a user id is on the wire and sets bad when the text is not a UUID.

net-transport-guide-bytes.salam
import net.transport

func main:
    mut b := transport.NewBytes(16)
    transport.Put8(b, 0x7f)
    transport.Put16(b, 443)
    transport.PutStr(b, "hi")
    println transport.ToHex(b), b.len
    println transport.Peek16(b, 1), transport.At(b, 3) as int
    transport.FreeBytes(b)

    a := transport.NewAddr("example.com", 443)
    println a.host, a.port, a.kind == transport.AddrDomain
    println transport.NewAddr("192.168.1.9", 80).kind == transport.AddrIPv4
    println transport.NewAddr("2001:db8::1", 8080).kind == transport.AddrIPv6

    mut wire := transport.NewBytes(32)
    transport.PutAddr(wire, a)
    println transport.ToHex(wire)
    mut back := transport.Addr {}
    used := transport.ReadAddr(wire, 0, back)
    println used, back.host, back.port
    transport.FreeBytes(wire)

    id := transport.ParseUUID("b831381d-6324-4d53-ad4f-8cda48b30811")
    println id.bad, id.len, transport.FormatUUID(id)
    println transport.ParseUUID("not a uuid").bad
end
Output7f01bb6869 5 443 104 example.com 443 true true true 01bb020b6578616d706c652e636f6d 15 example.com 443 false 16 b831381d-6324-4d53-ad4f-8cda48b30811 true

Quick start: a client and a server

Listen binds a port (0 picks a free one, and ListenerPort says which), Accept waits for a client, and Read and Write move bytes. Read returns 0 or less when the other side has closed. Check Ok after a dial, and read err when it is false. This program runs the server in a thread and talks to it over the loopback address.

net-transport-guide-echo.salam
import mem
import sync
import net.transport

mut port := 0
mut ready := sync.NewWaitGroup()

func echo_once():
    settings := transport.Settings {}
    l := transport.Listen(0, settings)
    port = transport.ListenerPort(l)
    sync.Done(ready)
    mut c := transport.Accept(l)
    buf := mem.Allocate(4096 as u64)
    n := transport.Read(c, buf, 4096)
    if n > 0:
        transport.Write(c, buf, n)
    end
    mem.Free(buf)
    transport.Close(c)
    transport.CloseListener(l)
end

func main:
    sync.Add(ready, 1 as i64)
    t := spawn(echo_once)
    sync.Wait(ready)

    mut c := transport.Dial("127.0.0.1", port, transport.Settings {})
    if not transport.Ok(c):
        println "dial failed:", c.err
        ret
    end
    mut msg := transport.FromStr("hello transport")
    transport.WriteBytes(c, msg)
    buf := mem.Allocate(4096 as u64)
    n := transport.Read(c, buf, 4096)
    println "echo of", n, "bytes:", (buf as str).substr(0, n)
    mem.Free(buf)
    transport.FreeBytes(msg)
    transport.Close(c)
    join(t)
end
Outputecho of 15 bytes: hello transport

WebSocket, and why Accept is not enough

Set network = "ws", a path and optionally a host on both sides. The client sends a normal-looking GET with a browser User-Agent. The server checks the path and, when host is set, the Host header; a mismatch is answered with an nginx style 404 Not Found page and the connection is closed, so a probe for a different path sees an ordinary web server.

The program below does the server side the careful way: AcceptRaw only takes the TCP connection (and the TLS handshake when tls is on), and Upgrade then reads the request. In a real server the Upgrade call and everything after it go into a worker thread per connection, so a client that stalls cannot hold up the others. The second half shows a wrong path being refused.

net-transport-guide-ws.salam
import mem
import sync
import net.transport

mut port := 0
mut ready := sync.NewWaitGroup()
mut seen_path := ""
mut seen_host := ""

func settings(): transport.Settings:
    mut s := transport.Settings {}
    s.network = "ws"
    s.path = "/tunnel"
    s.host = "cdn.example.com"
    ret s
end

func worker():
    s := settings()
    l := transport.Listen(0, s)
    port = transport.ListenerPort(l)
    sync.Done(ready)
    mut c := transport.AcceptRaw(l)
    if transport.Upgrade(c, s):
        seen_path = c.request_path
        seen_host = c.request_host
        buf := mem.Allocate(4096 as u64)
        n := transport.Read(c, buf, 4096)
        transport.Write(c, buf, n)
        mem.Free(buf)
    end
    transport.Close(c)
    transport.CloseListener(l)
end

func main:
    sync.Add(ready, 1 as i64)
    t := spawn(worker)
    sync.Wait(ready)

    mut c := transport.Dial("127.0.0.1", port, settings())
    mut msg := transport.FromStr("inside a websocket")
    transport.WriteBytes(c, msg)
    buf := mem.Allocate(4096 as u64)
    n := transport.Read(c, buf, 4096)
    println (buf as str).substr(0, n)
    mem.Free(buf)
    transport.FreeBytes(msg)
    transport.Close(c)
    join(t)
    println "server saw path", seen_path, "and host", seen_host

    mut wrong := settings()
    wrong.path = "/other"
    sync.Add(ready, 1 as i64)
    t2 := spawn(worker)
    sync.Wait(ready)
    bad := transport.Dial("127.0.0.1", port, wrong)
    println "wrong path accepted:", transport.Ok(bad)
    println bad.err
    join(t2)
end
Outputinside a websocket server saw path /tunnel and host cdn.example.com wrong path accepted: false upgrade refused: HTTP/1.1 404 Not Fou

early_data is the Xray style "0-RTT" trick: the first payload bytes ride in the Sec-WebSocket-Protocol header of the upgrade request. Pass them to DialEarly, and set early_data to the largest size you allow on both sides. The server hands those bytes back through the first Read.

VLESS or VMess over WebSocket and TLS behind a CDN

This is the usual way to run a proxy that has to survive: the real server hides behind a CDN, the connection to the CDN is ordinary HTTPS, and the CDN forwards WebSocket requests for one secret path to the server.

  1. Put a domain on the CDN and point it at the server. Turn the proxy (orange cloud) on and turn WebSockets on. Use a port the CDN accepts for HTTPS, such as 443, 2053, 2083, 2087 or 2096.
  2. Let the CDN terminate TLS. Choose its "Full" mode with an origin certificate, or run the server with tls = true and a certificate and key through cert_file and key_file. If you do not want the origin to speak TLS at all, listen with plain ws on a local port behind your own reverse proxy.
  3. On the server, Listen with network = "ws", a long random path and host set to your domain. Anything that asks for another path gets the nginx 404.
  4. On the client, dial the front name (any name that resolves to the CDN) with tls = true, sni set to the front name, host set to your own domain and the same path. The network between you and the CDN sees TLS to the front name only.
  5. Use alpn = "http/1.1". Set early_data only when the CDN passes the Sec-WebSocket-Protocol header through.
net-transport-guide-cdn.salam
import net.transport

func client_settings(): transport.Settings:
    mut s := transport.Settings {}
    s.network = "ws"
    s.tls = true
    s.sni = "front.example.net"
    s.host = "vpn.example.org"
    s.path = "/ws-8f3a"
    s.alpn = "http/1.1"
    s.early_data = 2048
    s.timeout_ms = 8000
    ret s
end

func server_settings(): transport.Settings:
    mut s := transport.Settings {}
    s.network = "ws"
    s.path = "/ws-8f3a"
    s.host = "vpn.example.org"
    ret s
end

func main:
    c := transport.Dial("front.example.net", 443, client_settings())
    println transport.Ok(c), c.err
    l := transport.Listen(10000, server_settings())
    println transport.ListenerOk(l)
    transport.CloseListener(l)
end

The program is checked but not run by the site, because it needs your server. See the VLESS and VMess pages for the same setup on top of a protocol, and the tunnel page to wire it into a local proxy. A CDN sees all your traffic in clear text between its two TLS legs: it terminates TLS for you, and the protocol inside (VMess) or the lack of one (VLESS with encryption=none) is all that is left. Prefer VMess with SecAES128GCM or SecChaCha20Poly1305 when you do not trust the CDN with your payload.

Fragmenting the TLS ClientHello

Some national filters, Iran's among them, read the server name out of the first packet of a TLS connection and cut the connection when the name is on a list. They often do this without reassembling TCP or TLS records, so a ClientHello that arrives in pieces is not matched. fragment = true does this on the client, in the TLS record layer, with no helper threads or sockets. fragment_mode chooses what is split:

  • "tcp" (the default): the hello record is written in several TCP segments of fragment_min to fragment_max random bytes, fragment_delay_ms apart.
  • "tls": the ClientHello record itself is split into several TLS records (each with its own header), which a server reassembles. The records go out one per segment.
  • "both": the record is split and the result is also cut into TCP segments.

fragment_sni = true adds a cut in the middle of the server name, so that no piece and no record holds it whole; with fragment_min and fragment_max both 0 it is the only cut. fragment_writes says how many of the first handshake writes are fragmented (1 is the ClientHello). The server sees the same handshake bytes either way; the tests show that a real TLS server completes the handshake, and that a sink sees the records reassemble to the identical ClientHello.

FragmentSizes is the function that picks the sizes. It is public so you can see what a setting produces: every piece but the last is within the range, and they add up to the total.

net-transport-guide-fragment.salam
import net.transport

func main:
    sizes := transport.FragmentSizes(517, 5, 20)
    mut total := 0
    mut in_range := true
    repeat sizes.len() in i:
        n := sizes.get(i)
        total += n
        if i < sizes.len() - 1 and (n < 5 or n > 20):
            in_range = false
        end
    end
    println "pieces add up to", total
    println "every piece but the last is 5 to 20 bytes:", in_range
    println "more than 25 pieces:", sizes.len() > 25
    println "one piece when min and max cover the whole message:", transport.FragmentSizes(10, 50, 60).len()
end
Outputpieces add up to 517 every piece but the last is 5 to 20 bytes: true more than 25 pieces: true one piece when min and max cover the whole message: 1

To turn it on for a TLS dial:

net-transport-guide-fragment-tls.salam
import net.transport

func main:
    mut s := transport.Settings {}
    s.tls = true
    s.sni = "example.com"
    s.fragment = true
    s.fragment_min = 3
    s.fragment_max = 30
    s.fragment_delay_ms = 5
    s.timeout_ms = 8000
    mut c := transport.Dial("example.com", 443, s)
    println transport.Ok(c), c.err
    transport.Close(c)
end

Start with pieces of 3 to 30 bytes and 5 to 10 milliseconds. Smaller pieces and longer pauses fool more filters and make the handshake slower; if the connection starts failing or timing out, grow the pieces or shorten the pause. Fragmentation applies to the client only, and only when tls = true. The ClientHello of the TLS 1.3 server in this library can be read from several records. It has to be tried where you are: a filter that reassembles the stream, or one that blocks on the IP address or on something other than the server name, is not affected, and what works changes over time. Fragmentation hides the name from the filter; it does not hide that you are using a proxy, and it does not change the shape of the handshake itself (see fingerprint above).

gRPC (gun and multi)

network = "grpc" carries the bytes in an HTTP/2 stream of gRPC messages, the V2Ray "gun" form: the client POSTs to /serviceName/Tun and each message holds one protobuf field with the data. grpc_mode = "multi" uses /serviceName/TunMulti. With tls = true the ALPN is h2 by default; without TLS it is cleartext HTTP/2. Set service_name on both sides (links carry it as serviceName); a request for another name is answered with a gRPC error and no connection is produced. The HTTP/2 framing is written in this package, with HPACK from net.http2, because the connection must be read and written from two threads at once. A server handles several streams on one HTTP/2 connection, and each becomes one connection from Accept. This is checked against itself only; it has not been run against a real Xray gRPC peer.

Mux.Cool

NewMuxClient and NewMuxServer multiplex sessions over one stream, in the framing V2Ray and Xray use for CmdMux: MuxOpen opens a session to a target and returns a stream, and MuxAccept on the other side returns each opened session with its target. vless.MuxClient and vmess.MuxClient (and MuxServer) wrap this for a connection made with CmdMux. There is no flow control between sessions, as in the original, so one stalled session can hold up the others, and the XUDP extensions (global ids, per-packet addresses) are not implemented. A UDP session carries one datagram per write; Xray drops the whole Mux connection when one is larger than 8192 bytes, so MuxMaxPacket is enforced: a larger write is refused (it returns -1) and the session stays open. Against Xray a UDP session works up to 8192 bytes, against sing-box up to 16000 were measured.

Reality

With security = "reality" a client runs the Reality handshake of net.reality in place of TLS (set reality with the public key and short id) and a listener does the server side from reality_server; a connection that does not authenticate is relayed to the cover site. Everything after the handshake is the same as for TLS.

Limits and security

  • Certificates are checked by default. allow_insecure = true turns the check off, which lets anyone on the path read and change the connection. Use it for a test against a self-signed certificate, or give the CA to ca_file instead.
  • Fragmentation touches only the first writes. Later TLS records are sealed and are never split; fragment_writes above 1 only matters for handshakes that send more than one flight.
  • The fake 404 is the whole disguise. A visitor with the right path and no WebSocket handshake gets 400 Bad Request in the same nginx style. Nothing else about the server is made to look like a website; if you need that, put a real reverse proxy in front.
  • A listener binds the loopback address by default. Listen uses bind (127.0.0.1); to accept connections from other machines call ListenAll or ListenAt with "0.0.0.0". Stalled clients are dropped by the handshake time limit; set read_timeout_ms for the idle ones.
  • No live comparison with Xray or V2Ray was run. The package is tested against itself and against byte vectors; whether a given CDN or a given Xray build accepts its requests has to be tried on your side.

Types

Settings

struct Settings
Everything that decides how a connection is made. The zero value is plain TCP.
NameTypeDescription
networkstr = "tcp""tcp", "ws" or "httpupgrade".
tlsbool = falseWrap the connection in TLS. A listener needs cert_file and key_file as well.
snistr = ""The server name sent in the TLS handshake and checked against the certificate. Empty means the dialed host.
alpnstr = ""The one ALPN protocol to offer, such as "http/1.1". It is a single string, not a list.
fingerprintstr = ""A browser name such as "chrome". Kept for links that carry it; it does not change the handshake.
allow_insecurebool = falseSkip certificate verification. Unsafe on a real network.
ca_filestr = ""A PEM file with the certificate authorities to trust instead of the system ones.
pathstr = "/"The request path of the WebSocket or upgrade request. The server refuses any other path.
hoststr = ""The Host header of the upgrade request. A server with this set refuses other host names.
headersstr = ""Extra request headers for the client, written as Name: value lines separated by \r\n.
early_dataint = 0The largest number of payload bytes sent in the Sec-WebSocket-Protocol header. 0 turns early data off.
fragmentbool = falseSplit the first bytes of the TLS handshake into small pieces. Client side, with tls only.
fragment_minint = 5The smallest piece in bytes.
fragment_maxint = 20The largest piece in bytes.
fragment_delay_msint = 10The pause between two pieces, in milliseconds.
cert_filestr = ""The server certificate chain, as a PEM file.
key_filestr = ""The server private key, as a PEM file.
timeout_msint = 0How long the TCP connect may take. 0 means the system default. It does not limit reads.
read_timeout_msint = 0Idle read timeout of an established connection, in milliseconds. 0 waits forever.
write_timeout_msint = 0Idle write timeout of an established connection, in milliseconds. 0 waits forever.
fragment_modestr = "tcp"What fragment splits: "tcp" (the default) writes the hello in several TCP segments, "tls" splits the ClientHello record into several TLS records, "both" does both.
fragment_snibool = falseAlso cut inside the server name, so no piece holds the whole name. With fragment_min and fragment_max both 0 this is the only cut.
fragment_writesint = 1How many of the first handshake writes are fragmented. The default 1 is the ClientHello.
bindstr = "127.0.0.1"The address a listener binds to. The default is 127.0.0.1; use ListenAll or "0.0.0.0" to listen on every interface.
handshake_timeout_msint = 10000How long a server lets a new connection take to finish the TLS and upgrade handshake before it drops it. 10000 by default.
max_handshakesint = 128How many handshakes a listener runs at the same time. Further connections are closed at once. 128 by default.
max_per_sourceint = 0How many handshakes one source address may have in progress at once. Further connections from it are closed at once. 0 means no limit.
securitystr = ""Empty for the plain or tls choice, or "reality" to run the Reality handshake instead of TLS.
realityreality.SettingsThe client Reality settings (net.reality), used when security is "reality".
reality_serverreality.ServerSettingsThe server Reality settings, used by a listener when security is "reality".
service_namestr = ""The gRPC service name for network = "grpc". The request path is /service/Tun (/service/TunMulti in multi mode).
grpc_modestr = "gun""gun" (the default) or "multi", for network = "grpc".

Conn

struct Conn
One connection, whatever carries it. Read and write it with the functions of this package, not through its fields.
NameTypeDescription
kindint = 0KindTCP, KindTLS or KindStream.
tcptcp.ConnThe TCP socket, when there is one.
tlstls.ConnThe TLS session, when there is one.
updyn tunnel.StreamThe stream under the connection when its kind is KindStream.
wsbool = falseTrue after a WebSocket upgrade: reads and writes then use frames.
clientbool = falseTrue on the side that dialed.
openbool = falseTrue while the connection is usable. Ok returns this.
errstr = ""The reason the dial, accept or upgrade failed, empty otherwise.
fr_leftint = 0Bytes of the current WebSocket frame not yet read.
fr_maskedbool = falseThe current frame is masked.
fr_maski64 = 0The mask of the current frame.
fr_posint = 0The position inside the current frame, for unmasking.
preBytesEarly data that arrived with the upgrade request. The first reads return it.
request_pathstr = ""On a server after the upgrade: the path the client asked for.
request_hoststr = ""On a server after the upgrade: the Host header the client sent.
wbufBytesThe reusable buffer WebSocket frames are built in before one write.
remotestr = ""The address of the peer, or empty when it is not known.
closedbool = falseTrue once Close has run, so closing again does nothing.

Listener

struct Listener
A listening socket with the settings it was made with.
NameTypeDescription
servertcp.ServerThe TCP server socket.
settingsSettingsThe settings given to Listen.
tls_cfgtls.ServerConfigThe server TLS configuration loaded from the certificate and key.
readychan.ChanThe queue of connections whose handshake finished, which Accept takes from.
flagsi64*Shared counters of the accept loop: started, stopped.
slotssync.SemaphoreThe limit on handshakes in progress at the same time.
sourcestunnel.SourceTableThe handshakes in progress per source address.

MuxIncoming

struct MuxIncoming
A session the client opened: its stream, its target and whether it is UDP.
NameTypeDescription
streamdyn tunnel.StreamThe stream of the session. Closing it ends the session only.
addrAddrThe target the client asked for.
udpbool = falseTrue when each write and read of the stream is one datagram.

Mux

struct Mux
A Mux.Cool multiplexer on one carrier connection: many sessions, each a stream.
NameTypeDescription
corevoid*The shared state of the multiplexer.
okbool = falseTrue when the multiplexer was created.

Addr

struct Addr
A target address: kind, host text and port.
NameTypeDescription
kindint = 2AddrIPv4, AddrDomain or AddrIPv6.
hoststr = ""The address or name as text.
portint = 0The port.

Bytes

struct Bytes
A growable byte buffer. It owns memory: free it with FreeBytes when you are done.
NameTypeDescription
pvoid*The start of the memory.
lenint = 0How many bytes are in use.
capint = 0How many bytes fit before the buffer grows.
badbool = falseTrue when the buffer is the result of a failed parse or conversion, such as an invalid hex text or UUID.

Constants and variables

Connection kinds Kind…

The value of the kind field of a connection: KindTCP (0) plain TCP, KindTLS (1) TLS, KindStream (2) a net.tunnel stream.
NameValue
KindTCP0
KindTLS1
KindStream2

MuxMaxPacket

const MuxMaxPacket: i32 = 8192
The largest datagram a UDP session takes in one write (8192, the limit of Xray). A larger write returns -1 and sends nothing.

AddrIPv4

const AddrIPv4: i32 = 1
The address kind number of an IPv4 address.

AddrDomain

const AddrDomain: i32 = 2
The address kind number of a domain name.

AddrIPv6

const AddrIPv6: i32 = 3
The address kind number of an IPv6 address.

Functions

FragmentSizes

func FragmentSizes(total: int, min: int, max: int): Vector<int>
The piece sizes a fragmented write of total bytes would use: each piece is between min and max bytes, except possibly the last one, and they add up to total. The sizes are random.

Ok

func Ok(c: Conn): bool
True when the connection is open.

Read

func Read(c &: Conn, buf: void*, max: int): int
Reads up to max bytes and returns how many arrived. A WebSocket connection returns the payload of binary frames and answers pings by itself. 0 or less means the connection ended.

ReadFull

func ReadFull(c &: Conn, buf: void*, n: int): bool
Reads exactly n bytes. False when the connection ended first.

Write

func Write(c &: Conn, p: void*, n: int): bool
Writes n bytes from a pointer, as one WebSocket frame on a WebSocket connection. False when it fails.

WriteBytes

func WriteBytes(c &: Conn, b: Bytes): bool
Writes a whole buffer.

SwitchDirect

func SwitchDirect(c &: Conn): bool
Makes later reads come straight from the TLS or Reality socket once an XTLS Vision peer sent its direct command. Returns false for a connection that cannot do that (WebSocket or plain TCP).

SetReadTimeout

func SetReadTimeout(c &: Conn, ms: int)
Makes a read give up after this many milliseconds. 0 waits forever.

Close

func Close(c &: Conn)
Closes the connection; a WebSocket connection sends a close frame first.

DialEarly

func DialEarly(host: str, port: int, s: Settings, early: Bytes): Conn
Dials like Dial and sends early in the upgrade request when the settings allow early data.

Dial

func Dial(host: str, port: int, s: Settings): Conn
Connects to a host and port: TCP, then ClientHello fragmentation, TLS and the upgrade request, as the settings ask. Check Ok and read err when it is false.

ListenAt

func ListenAt(host: str, port: int, s: Settings): Listener
Starts listening on a given IPv4 address and port. An empty address means loopback and "all" or "0.0.0.0" means every interface.

ListenerFrom

func ListenerFrom(server: tcp.Server, s: Settings): Listener
Builds a listener from an already bound TCP server (for example one bound with rawsock.ListenOn), with the accept queue, handshake limit and server TLS ready. net.vpn uses it.

Listen

func Listen(port: int, s: Settings): Listener
Starts listening on a port; 0 picks a free one.

ListenAll

func ListenAll(port: int, s: Settings): Listener
Like Listen but on every network interface. This is the explicit opt-in: Listen itself binds the loopback address only.

ListenerOk

func ListenerOk(l: Listener): bool
True when the socket is bound and, with TLS, the certificate and key loaded.

ListenerPort

func ListenerPort(l: Listener): int
The port the listener is bound to.

CloseListener

func CloseListener(l: Listener)
Stops listening.

AcceptRaw

func AcceptRaw(l: Listener): Conn
Waits for a client and finishes the TCP connection and the TLS handshake, but not the upgrade request.

Upgrade

func Upgrade(c &: Conn, s: Settings): bool
Reads the upgrade request on a connection from AcceptRaw, checks the path and host, answers it and switches the connection to WebSocket or raw mode. Does nothing for plain tcp. A refused request is answered and the connection is closed.

ShutdownConn

func ShutdownConn(c &: Conn)
Shuts the socket of a TCP or TLS connection down in both directions without freeing it, so a Read waiting in another thread returns 0 or less and the peer sees the end. Call Close afterwards. A connection over a net.tunnel stream is left as it is.
net-transport-CloseWriteConn.salam
import mem
import sync
import net.transport

mut port := 0
mut ready := sync.NewWaitGroup()

func count_then_answer():
    l := transport.Listen(0, transport.Settings {})
    port = transport.ListenerPort(l)
    sync.Done(ready)
    mut c := transport.Accept(l)
    buf := mem.Allocate(4096 as u64)
    mut total := 0
    mut n := transport.Read(c, buf, 4096)
    until n > 0:
        total += n
        n = transport.Read(c, buf, 4096)
    end
    mem.Free(buf)
    mut reply := transport.FromStr("server read " + total + " bytes")
    transport.WriteBytes(c, reply)
    transport.FreeBytes(reply)
    transport.ShutdownConn(c)
    transport.Close(c)
    transport.CloseListener(l)
end

func main:
    sync.Add(ready, 1 as i64)
    t := spawn(count_then_answer)
    sync.Wait(ready)

    mut c := transport.Dial("127.0.0.1", port, transport.Settings {})
    if not transport.Ok(c):
        println "dial failed:", c.err
        ret
    end
    mut msg := transport.FromStr("first part, second part")
    transport.WriteBytes(c, msg)
    transport.FreeBytes(msg)
    transport.CloseWriteConn(c)
    buf := mem.Allocate(4096 as u64)
    n := transport.Read(c, buf, 4096)
    println (buf as str).substr(0, n)
    println "then", transport.Read(c, buf, 4096)
    mem.Free(buf)
    transport.Close(c)
    join(t)
end
Outputserver read 23 bytes then 0

CloseWriteConn

func CloseWriteConn(c &: Conn)
Tells the peer that nothing more will be sent while you keep reading its answer: TLS sends its close_notify alert, TCP shuts down its write side and a net.tunnel stream passes the call on. The peer's Read then returns 0.

The example for this item is shown with the ones above: net-transport-CloseWriteConn.salam

Accept

func Accept(l: Listener): Conn
AcceptRaw followed by Upgrade, in the calling thread. A client that stalls holds up the caller.

AsStream

func AsStream(c: Conn): dyn tunnel.Stream
Wraps a connection as a tunnel.Stream, so tunnel code can use it.

FromStream

func FromStream(s: dyn tunnel.Stream, client: bool): Conn
Uses a tunnel.Stream as the carrier of a connection. Set client to say which side it is.

NewMuxClient

func NewMuxClient(up: dyn tunnel.Stream): Mux
Starts the client side of a multiplexer on a stream, with a reader thread.

NewMuxServer

func NewMuxServer(up: dyn tunnel.Stream): Mux
Starts the server side of a multiplexer on a stream, with a reader thread.

MuxOpen

func MuxOpen(m: Mux, addr: Addr, udp: bool): dyn tunnel.Stream
Client side: opens a session to a target and returns its stream. The stream reports not ok when the carrier is gone.

MuxAccept

func MuxAccept(m: Mux, out &: MuxIncoming): bool
Server side: waits for the client to open a session and fills out with its stream and target. False when the carrier ended.

MuxOk

func MuxOk(m: Mux): bool
True while the carrier is alive.

MuxClose

func MuxClose(m: Mux)
Shuts the carrier, which ends every session.

DetectKind

func DetectKind(host: str): int
Decides whether a host text is an IPv4 address, an IPv6 address or a domain name.

NewAddr

func NewAddr(host: str, port: int): Addr
Builds an address and sets its kind from the text.

PutAddr

func PutAddr(b &: Bytes, a: Addr): bool
Appends the wire form: port, kind byte and address. A domain is prefixed by its length. False when the address cannot be written, for example an empty name, a name longer than 255 bytes or an IP address that does not parse.

FormatIPv6

func FormatIPv6(p: u8*): str
Formats 16 bytes as IPv6 text.

ReadAddr

func ReadAddr(b: Bytes, off: int, a &: Addr): int
Reads an address from a buffer at an offset and returns the offset just after the address, or -1 when the bytes are not a valid address.

ParseUUID

func ParseUUID(s: str): Bytes
Turns the 36 character UUID text into 16 bytes; bad is set on any other text.

FormatUUID

func FormatUUID(b: Bytes): str
Turns 16 bytes into the 36 character UUID text.

NewBytes

func NewBytes(cap0: int): Bytes
Allocates an empty buffer with room for at least cap0 bytes.

FreeBytes

func FreeBytes(b &: Bytes)
Releases the memory and resets the buffer.

ResetBytes

func ResetBytes(b &: Bytes)
Empties the buffer and keeps its memory.

BytesPtr

func BytesPtr(b: Bytes, off: int): void*
A pointer to the byte at offset off, for passing to a function that fills or reads memory.

Reserve

func Reserve(b &: Bytes, need: int)
Makes sure need more bytes fit without growing again.

Put8

func Put8(b &: Bytes, v: int)
Appends one byte.

Put16

func Put16(b &: Bytes, v: int)
Appends a 16 bit number, high byte first.

Put32

func Put32(b &: Bytes, v: i64)
Appends a 32 bit number, high byte first.

Put64

func Put64(b &: Bytes, v: u64)
Appends a 64 bit number, high byte first.

PutRaw

func PutRaw(b &: Bytes, src: void*, n: int)
Appends n bytes from a pointer.

PutBytes

func PutBytes(b &: Bytes, src: Bytes)
Appends another buffer.

PutStr

func PutStr(b &: Bytes, s: str)
Appends the bytes of a text.

PutZeros

func PutZeros(b &: Bytes, n: int)
Appends n zero bytes.

At

func At(b: Bytes, i: int): int
The byte at index i as a number from 0 to 255.

Set8

func Set8(b &: Bytes, i: int, v: int)
Overwrites the byte at index i.

Peek16

func Peek16(b: Bytes, i: int): int
Reads a 16 bit number at an offset, high byte first.

Peek32

func Peek32(b: Bytes, i: int): i64
Reads a 32 bit number at an offset, high byte first.

Consume

func Consume(b &: Bytes, n: int)
Drops the first n bytes.

Equal

func Equal(a: Bytes, b: Bytes): bool
True when two buffers hold the same bytes. The comparison does not stop at the first difference.

ToHex

func ToHex(b: Bytes): str
The buffer as lower case hex text.

FromHex

func FromHex(h: str): Bytes
Reads hex text into a new buffer; bad is set when the text is not valid hex.

FromStr

func FromStr(s: str): Bytes
Copies a text into a new buffer.