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.
DialandListenwork over plain TCP, TLS, WebSocket (alone or inside TLS) and a bare HTTP upgrade.FromStreamandAsStreamlet 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 HTTPHostheader 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.
fragmentsends 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.
Bytesis the growable buffer the protocol packages build headers in, andAddris 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.
| Settings | Carrier |
|---|---|
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 = true | The 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
Accepthands 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 mostmax_handshakes(128) run at once; a client that stalls is dropped and a connection over the limit is closed at once.Acceptthen returns the next finished connection, or a closed one whoseerrsays why a handshake failed.AcceptRawandUpgradestill exist for a loop of your own.sniis nothost.sniis the name in the TLS handshake and the name the certificate is checked against;hostis theHostheader 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. fingerprintchanges nothing. The field is kept so links that carryfp=chromeround-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_mscovers the connect only. For an established connection setread_timeout_msandwrite_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.
NewBytesallocates andFreeBytesfrees; 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.
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
endQuick 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.
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)
endWebSocket, 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.
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)
endearly_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.
- 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.
- Let the CDN terminate TLS. Choose its "Full" mode with an origin certificate, or run the server with
tls = trueand a certificate and key throughcert_fileandkey_file. If you do not want the origin to speak TLS at all, listen with plainwson a local port behind your own reverse proxy. - On the server,
Listenwithnetwork = "ws", a long randompathandhostset to your domain. Anything that asks for another path gets the nginx 404. - On the client, dial the front name (any name that resolves to the CDN) with
tls = true,sniset to the front name,hostset to your own domain and the samepath. The network between you and the CDN sees TLS to the front name only. - Use
alpn = "http/1.1". Setearly_dataonly when the CDN passes theSec-WebSocket-Protocolheader through.
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)
endThe 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 offragment_mintofragment_maxrandom bytes,fragment_delay_msapart."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.
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()
endTo turn it on for a TLS dial:
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)
endStart 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 = trueturns 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 toca_fileinstead. - Fragmentation touches only the first writes. Later TLS records are sealed and are never split;
fragment_writesabove 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 Requestin 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.
Listenusesbind(127.0.0.1); to accept connections from other machines callListenAllorListenAtwith"0.0.0.0". Stalled clients are dropped by the handshake time limit; setread_timeout_msfor 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| Name | Type | Description |
|---|---|---|
network | str = "tcp" | "tcp", "ws" or "httpupgrade". |
tls | bool = false | Wrap the connection in TLS. A listener needs cert_file and key_file as well. |
sni | str = "" | The server name sent in the TLS handshake and checked against the certificate. Empty means the dialed host. |
alpn | str = "" | The one ALPN protocol to offer, such as "http/1.1". It is a single string, not a list. |
fingerprint | str = "" | A browser name such as "chrome". Kept for links that carry it; it does not change the handshake. |
allow_insecure | bool = false | Skip certificate verification. Unsafe on a real network. |
ca_file | str = "" | A PEM file with the certificate authorities to trust instead of the system ones. |
path | str = "/" | The request path of the WebSocket or upgrade request. The server refuses any other path. |
host | str = "" | The Host header of the upgrade request. A server with this set refuses other host names. |
headers | str = "" | Extra request headers for the client, written as Name: value lines separated by \r\n. |
early_data | int = 0 | The largest number of payload bytes sent in the Sec-WebSocket-Protocol header. 0 turns early data off. |
fragment | bool = false | Split the first bytes of the TLS handshake into small pieces. Client side, with tls only. |
fragment_min | int = 5 | The smallest piece in bytes. |
fragment_max | int = 20 | The largest piece in bytes. |
fragment_delay_ms | int = 10 | The pause between two pieces, in milliseconds. |
cert_file | str = "" | The server certificate chain, as a PEM file. |
key_file | str = "" | The server private key, as a PEM file. |
timeout_ms | int = 0 | How long the TCP connect may take. 0 means the system default. It does not limit reads. |
read_timeout_ms | int = 0 | Idle read timeout of an established connection, in milliseconds. 0 waits forever. |
write_timeout_ms | int = 0 | Idle write timeout of an established connection, in milliseconds. 0 waits forever. |
fragment_mode | str = "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_sni | bool = false | Also 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_writes | int = 1 | How many of the first handshake writes are fragmented. The default 1 is the ClientHello. |
bind | str = "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_ms | int = 10000 | How long a server lets a new connection take to finish the TLS and upgrade handshake before it drops it. 10000 by default. |
max_handshakes | int = 128 | How many handshakes a listener runs at the same time. Further connections are closed at once. 128 by default. |
max_per_source | int = 0 | How many handshakes one source address may have in progress at once. Further connections from it are closed at once. 0 means no limit. |
security | str = "" | Empty for the plain or tls choice, or "reality" to run the Reality handshake instead of TLS. |
reality | reality.Settings | The client Reality settings (net.reality), used when security is "reality". |
reality_server | reality.ServerSettings | The server Reality settings, used by a listener when security is "reality". |
service_name | str = "" | The gRPC service name for network = "grpc". The request path is /service/Tun (/service/TunMulti in multi mode). |
grpc_mode | str = "gun" | "gun" (the default) or "multi", for network = "grpc". |
Conn
struct Conn| Name | Type | Description |
|---|---|---|
kind | int = 0 | KindTCP, KindTLS or KindStream. |
tcp | tcp.Conn | The TCP socket, when there is one. |
tls | tls.Conn | The TLS session, when there is one. |
up | dyn tunnel.Stream | The stream under the connection when its kind is KindStream. |
ws | bool = false | True after a WebSocket upgrade: reads and writes then use frames. |
client | bool = false | True on the side that dialed. |
open | bool = false | True while the connection is usable. Ok returns this. |
err | str = "" | The reason the dial, accept or upgrade failed, empty otherwise. |
fr_left | int = 0 | Bytes of the current WebSocket frame not yet read. |
fr_masked | bool = false | The current frame is masked. |
fr_mask | i64 = 0 | The mask of the current frame. |
fr_pos | int = 0 | The position inside the current frame, for unmasking. |
pre | Bytes | Early data that arrived with the upgrade request. The first reads return it. |
request_path | str = "" | On a server after the upgrade: the path the client asked for. |
request_host | str = "" | On a server after the upgrade: the Host header the client sent. |
wbuf | Bytes | The reusable buffer WebSocket frames are built in before one write. |
remote | str = "" | The address of the peer, or empty when it is not known. |
closed | bool = false | True once Close has run, so closing again does nothing. |
Listener
struct Listener| Name | Type | Description |
|---|---|---|
server | tcp.Server | The TCP server socket. |
settings | Settings | The settings given to Listen. |
tls_cfg | tls.ServerConfig | The server TLS configuration loaded from the certificate and key. |
ready | chan.Chan | The queue of connections whose handshake finished, which Accept takes from. |
flags | i64* | Shared counters of the accept loop: started, stopped. |
slots | sync.Semaphore | The limit on handshakes in progress at the same time. |
sources | tunnel.SourceTable | The handshakes in progress per source address. |
MuxIncoming
struct MuxIncoming| Name | Type | Description |
|---|---|---|
stream | dyn tunnel.Stream | The stream of the session. Closing it ends the session only. |
addr | Addr | The target the client asked for. |
udp | bool = false | True when each write and read of the stream is one datagram. |
Mux
struct Mux| Name | Type | Description |
|---|---|---|
core | void* | The shared state of the multiplexer. |
ok | bool = false | True when the multiplexer was created. |
Addr
struct Addr| Name | Type | Description |
|---|---|---|
kind | int = 2 | AddrIPv4, AddrDomain or AddrIPv6. |
host | str = "" | The address or name as text. |
port | int = 0 | The port. |
Bytes
struct BytesFreeBytes when you are done.| Name | Type | Description |
|---|---|---|
p | void* | The start of the memory. |
len | int = 0 | How many bytes are in use. |
cap | int = 0 | How many bytes fit before the buffer grows. |
bad | bool = false | True 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…
kind field of a connection: KindTCP (0) plain TCP, KindTLS (1) TLS, KindStream (2) a net.tunnel stream.| Name | Value |
|---|---|
KindTCP | 0 |
KindTLS | 1 |
KindStream | 2 |
MuxMaxPacket
const MuxMaxPacket: i32 = 8192AddrIPv4
const AddrIPv4: i32 = 1AddrDomain
const AddrDomain: i32 = 2AddrIPv6
const AddrIPv6: i32 = 3Functions
FragmentSizes
func FragmentSizes(total: int, min: int, max: int): Vector<int>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): boolRead
func Read(c &: Conn, buf: void*, max: int): intmax 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): booln bytes. False when the connection ended first.Write
func Write(c &: Conn, p: void*, n: int): booln bytes from a pointer, as one WebSocket frame on a WebSocket connection. False when it fails.WriteBytes
func WriteBytes(c &: Conn, b: Bytes): boolSwitchDirect
func SwitchDirect(c &: Conn): boolSetReadTimeout
func SetReadTimeout(c &: Conn, ms: int)Close
func Close(c &: Conn)DialEarly
func DialEarly(host: str, port: int, s: Settings, early: Bytes): ConnDial and sends early in the upgrade request when the settings allow early data.Dial
func Dial(host: str, port: int, s: Settings): ConnOk and read err when it is false.ListenAt
func ListenAt(host: str, port: int, s: Settings): Listener"all" or "0.0.0.0" means every interface.ListenerFrom
func ListenerFrom(server: tcp.Server, s: Settings): Listenerrawsock.ListenOn), with the accept queue, handshake limit and server TLS ready. net.vpn uses it.Listen
func Listen(port: int, s: Settings): ListenerListenAll
func ListenAll(port: int, s: Settings): ListenerListen but on every network interface. This is the explicit opt-in: Listen itself binds the loopback address only.ListenerOk
func ListenerOk(l: Listener): boolListenerPort
func ListenerPort(l: Listener): intCloseListener
func CloseListener(l: Listener)AcceptRaw
func AcceptRaw(l: Listener): ConnUpgrade
func Upgrade(c &: Conn, s: Settings): boolAcceptRaw, 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)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.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)
endCloseWriteConn
func CloseWriteConn(c &: Conn)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): ConnAcceptRaw followed by Upgrade, in the calling thread. A client that stalls holds up the caller.AsStream
func AsStream(c: Conn): dyn tunnel.Streamtunnel.Stream, so tunnel code can use it.FromStream
func FromStream(s: dyn tunnel.Stream, client: bool): Conntunnel.Stream as the carrier of a connection. Set client to say which side it is.NewMuxClient
func NewMuxClient(up: dyn tunnel.Stream): MuxNewMuxServer
func NewMuxServer(up: dyn tunnel.Stream): MuxMuxOpen
func MuxOpen(m: Mux, addr: Addr, udp: bool): dyn tunnel.StreamMuxAccept
func MuxAccept(m: Mux, out &: MuxIncoming): boolout with its stream and target. False when the carrier ended.MuxOk
func MuxOk(m: Mux): boolMuxClose
func MuxClose(m: Mux)DetectKind
func DetectKind(host: str): intNewAddr
func NewAddr(host: str, port: int): AddrPutAddr
func PutAddr(b &: Bytes, a: Addr): boolFormatIPv6
func FormatIPv6(p: u8*): strReadAddr
func ReadAddr(b: Bytes, off: int, a &: Addr): intParseUUID
func ParseUUID(s: str): Bytesbad is set on any other text.FormatUUID
func FormatUUID(b: Bytes): strNewBytes
func NewBytes(cap0: int): Bytescap0 bytes.FreeBytes
func FreeBytes(b &: Bytes)ResetBytes
func ResetBytes(b &: Bytes)BytesPtr
func BytesPtr(b: Bytes, off: int): void*off, for passing to a function that fills or reads memory.Reserve
func Reserve(b &: Bytes, need: int)need more bytes fit without growing again.Put8
func Put8(b &: Bytes, v: int)Put16
func Put16(b &: Bytes, v: int)Put32
func Put32(b &: Bytes, v: i64)Put64
func Put64(b &: Bytes, v: u64)PutRaw
func PutRaw(b &: Bytes, src: void*, n: int)n bytes from a pointer.PutBytes
func PutBytes(b &: Bytes, src: Bytes)PutStr
func PutStr(b &: Bytes, s: str)PutZeros
func PutZeros(b &: Bytes, n: int)n zero bytes.At
func At(b: Bytes, i: int): inti as a number from 0 to 255.Set8
func Set8(b &: Bytes, i: int, v: int)i.Peek16
func Peek16(b: Bytes, i: int): intPeek32
func Peek32(b: Bytes, i: int): i64Consume
func Consume(b &: Bytes, n: int)n bytes.Equal
func Equal(a: Bytes, b: Bytes): boolToHex
func ToHex(b: Bytes): strFromHex
func FromHex(h: str): Bytesbad is set when the text is not valid hex.FromStr
func FromStr(s: str): Bytes