Standard library · Network

net.tunnel

The shared core for proxy and VPN style tools: streams, addresses, dialers and proxy chains, a mixed inbound, routing rules with an Iran preset, share links and v2ray style configs.

import net.tunnel

This package is the common core of Salam's proxy tools. It does not speak one proxy protocol itself. It gives the packages of this family one shared shape, so any inbound (the side that accepts clients) can be joined to any outbound (the side that reaches the world): net.socks, net.httpproxy and net.trojan all plug into it, and so will the protocol packages that follow. To see how the pieces fit together in an app, read Building a v2ray style or VPN app.

It is a toolkit for a proxy, not for a system wide VPN: there is no virtual network interface, so an application has to be pointed at the local port (SOCKS or HTTP) or be proxy aware. Its purpose is privacy and access to information. Use it on networks and with servers you are allowed to use, and keep the people who rely on it safe.

application  ->  inbound  ->  router  ->  outbound  ->  remote server  ->  target
             mixed       rules      direct, proxy chain, block
             SOCKS/HTTP  .ir direct

Core ideas:

  • A Stream is a two way byte connection. TCP, TLS, WebSocket and in memory pipes all look the same, so a protocol or a relay never cares what is underneath.
  • An Addr is a target: a host name or IP and a port. Every dialer and every inbound speaks in Addr values.
  • A Dialer opens an outgoing connection. DirectDialer, BlockDialer, Chain (several proxy hops in a row), Fallback and RouterDialer are all dialers, and they nest. A Connector is one proxy hop that a chain can use.
  • An InboundProtocol reads a client's request and answers it; ListenTcp accepts connections and a Handler (usually ForwardHandler) serves them. MixedProtocol puts SOCKS4, SOCKS5 and HTTP proxy on one port.
  • A Router turns a target into a tag (direct, proxy, block) using ordered rules, with IranRouter as a ready preset.
  • ParseLink and ParseSubscription read vmess://, vless://, trojan:// and ss:// share links, and ParseConfig reads v2ray style JSON.

Things that catch people out:

  • Failure is a closed stream, not an exception. A dial that fails returns a stream whose Ok is false. Check it before you write.
  • A config does not build protocols by itself. BuildDialer knows freedom (direct) and blackhole (block). Any other outbound protocol needs a maker registered with RegisterOutbound; until one is, that outbound refuses traffic instead of leaking it. The packages in this family give you dialers and connectors you can build in code, as the guide below does.
  • It is TCP. Streams carry TCP. UDP relays are the business of the individual protocol packages.
  • Examples use IPv4 loopback; tests cover IPv6 too. Listeners accept ::1 and :: (both families), DirectDialer reaches IPv6 targets and races IPv6 and IPv4 addresses of a name, and the per-source limit treats IPv4-mapped clients as IPv4 and groups IPv6 clients by their /64 prefix.
  • Share links are untrusted input. ParseLink rejects what it cannot read, but a subscription comes from someone else's server. Look at allow_insecure before you trust a node.
  • A Stats value and a IdleTimer own native counters. Free them with FreeStats and Free when you are done.

Building a v2ray style or VPN app

A v2ray style app is a small pipeline: something accepts the application's connection (inbound), something decides where it should go (router), and something carries it there (outbound). In Salam each piece is a value, and this package joins them.

              +--------------------------- your process ----------------------------+
application   |  inbound          router              outbound                   |   remote server
(browser,     |  MixedProtocol  Router            Dialer                   |   (trojan, ...)
 curl, app) -> |  SOCKS4/5 + HTTP  rules decide a tag  direct                    | ->      |
127.0.0.1:1080|  -> Session      .ir, private: direct  Via(hop), Chain         |           -> target
              |     target Addr everything else: proxy BlockDialer           |
              +----------------------------------------------------------------------+
                         ForwardHandler relays bytes both ways (Relay)

The example runs this whole pipeline on loopback. Two echo servers stand in for the Internet: local is reached directly and abroad only through a Trojan server, which the router chooses by port. A mixed inbound takes a SOCKS5 client and an HTTP CONNECT client on the same port.

net-tunnel-guide-app.salam
import net.tcp
import net.socks
import net.httpproxy
import net.trojan
import net.tunnel
import sync

mut local_port := 0
mut abroad_port := 0
mut ready := sync.NewWaitGroup()

func serve_echo(name: str, port: int):
    srv := tcp.Bind(port)
    if name == "local":
        local_port = tcp.Port(srv)
    else:
        abroad_port = tcp.Port(srv)
    end
    sync.Done(ready)
    until true:
        c := tcp.Accept(srv)
        data := tcp.Read(c, 100)
        tcp.Write(c, name + " saw " + data)
        tcp.Close(c)
    end
end

func local_echo():
    serve_echo("local", 0)
end

func abroad_echo():
    serve_echo("abroad", 0)
end

func ask(proxy_port: int, port: int, text: str): str:
    r := socks.Connect(socks.NewClient("127.0.0.1", proxy_port), "127.0.0.1", port)
    r.stream.Write(text)
    answer := r.stream.Read(100)
    r.stream.Close()
    ret answer
end

func main:
    sync.Add(ready, 2 as i64)
    _a := spawn(local_echo)
    _b := spawn(abroad_echo)
    sync.Wait(ready)

    mut remote_cfg := trojan.NewServerConfig("", "")
    trojan.AddPassword(remote_cfg, "secret")
    remote := trojan.ServePlain(remote_cfg, 0)

    rules := tunnel.ParseRules("DST-PORT," + abroad_port + ",proxy\nDOMAIN-SUFFIX,ir,direct\nFINAL,direct\n", "direct")
    println "rule errors:", rules.errors.len()

    mut dialer := tunnel.NewRouterDialer(rules.router)
    tunnel.RouterDialerAdd(dialer, "direct", tunnel.Direct())
    hop := trojan.NewConnector("127.0.0.1", remote.port, "secret")
    tunnel.RouterDialerAdd(dialer, "proxy", tunnel.Via(tunnel.Direct(), hop))

    mut mixed := tunnel.NewMixed()
    tunnel.MixedSetSocks5(mixed, socks.NewInbound(socks.NewServerConfig(0)))
    tunnel.MixedSetHttp(mixed, httpproxy.NewInbound(httpproxy.NewServerConfig(0)))
    forward := tunnel.NewForwarder(dialer, mixed)
    inbound := tunnel.ListenTcp(0)
    tunnel.ServeBackground(inbound, mixed, forward)

    println ask(inbound.port, local_port, "hello")
    println ask(inbound.port, abroad_port, "hello")

    web := httpproxy.Connect(httpproxy.NewClient("127.0.0.1", inbound.port), "127.0.0.1", abroad_port)
    println "http connect:", web.status
    web.stream.Write("via http")
    println web.stream.Read(100)
    web.stream.Close()

    sync.SleepMs(100)
    println "connections:", forward.stats.Total()
    inbound.Stop()
    trojan.Stop(remote)
end
Outputrule errors: 0 local saw hello abroad saw hello http connect: 200 abroad saw via http connections: 3

The pieces, in order: ParseRules builds the Router; a RouterDialer maps each tag to a dialer (Direct for direct, Via with a trojan Connector for proxy); NewMixed with MixedSetSocks5 and MixedSetHttp is the inbound protocol; NewForwarder joins the dialer and the protocol into a handler; ListenTcp and ServeBackground start serving. To add more hops use Chain; to try several outbounds in turn use Fallback.

Practical advice for Iran

  • Send domestic traffic direct. IranRouter sends .ir (and the Persian .ایران forms), private addresses and a few large domestic services straight out, and everything else to your proxy tag. That keeps banking, delivery and government sites working at normal speed and with their normal IP, and keeps the load on your server down. Add your own lines with RuleSuffix or a rule file for ParseRules.
  • Use TLS, and give the server a real face. A Trojan server that holds a real certificate and a real website behind it (trojan.SetFallback) looks like any HTTPS site to a probe: anything that is not an authenticated client is handed to that site. Use a domain you own, a valid certificate and a fallback that serves actual content.
  • Fragment the TLS hello. Some filters read the server name in the first TLS record. net.censor can split that record into pieces (censor.FragmentClientHello, censor.SNISplitSizes) before it is sent. Treat it as one tool that works on some networks and not on others, and test it where you are.
  • Resolve names over an encrypted channel. Plain DNS on UDP port 53 is readable and easy to tamper with. net.doh resolves over DNS over HTTPS or TLS, caches answers, and can run a local stub resolver for other programs. Resolve through the proxy side where you can, so that the name lookup does not reveal what you visit.
  • Keep the rules honest. A proxy that sends everything out hurts everyday browsing, and one that sends too much direct leaks. Review your rule list the same way you review a firewall.
  • Protect the people on the other end. Do not log targets, rotate passwords, and do not share a server with a shared password across people you cannot vouch for.

This package is a tool for privacy and for reaching information. It cannot make you anonymous, it does not hide that you use a proxy from your network provider, and it is not meant for abuse of other people's services or networks.

From a config or a share link

If your app takes a subscription, ParseSubscription gives a list of Node values and ConfigFromNodes turns them into a ready Config (a mixed inbound, one outbound per node, direct and block, and the .ir rule). BuildDialer then builds the dialer, but only for outbound protocols that have a registered maker. Writing a maker is a few lines, see OutboundMaker.

Quick start: building a proxy or VPN app with Salam

This is the short path from a share link to a local proxy that a browser or any other program can connect to. Each step is one call, and the program below puts them together.

  1. Read the link or the subscription. ParseLink reads one vless://, vmess://, trojan:// or ss:// link and ParseSubscription reads a text with many links (Base64 or one link per line). A bad line is dropped and counted in skipped; the program does not stop. A link that comes from someone else is untrusted input: look at allow_insecure before you trust a node.
  2. Build a config. ConfigFromNodes makes a mixed inbound (SOCKS4, SOCKS5 and HTTP on one local port), one outbound per node, the direct and block outbounds and the .ir rule. With no extra work, .ir domains and private addresses go direct and everything else goes through your node.
  3. Turn on DoH and fragmenting. With RegisterWith, the fragment option sends the ClientHello in several packets and use_doh resolves names with DNS over HTTPS. Both are only options: they help on some networks and not on others.
  4. Start. StartAll brings the inbounds up and Stop closes each one. If the config is a V2Ray style JSON file, RunConfig loads it, checks it and runs it until Ctrl-C.
  5. Connect your programs. The inbound listens on 127.0.0.1 only. In a browser or an app, set the SOCKS5 or HTTP proxy to that port. To test from a command line, curl --socks5-hostname 127.0.0.1:1080 https://example.com resolves the name through the proxy too.
net-tunnel-guide-quickstart.salam
import net.doh
import net.tunnel
import net.vpn

func main:
    links := "vless://b831381d-6324-4d53-ad4f-8cda48b30811@vpn.example.org:443?security=tls&type=ws&host=vpn.example.org&path=%2Fws&sni=front.example.net#My Server\ntrojan://s3cret@vpn.example.org:443?sni=vpn.example.org#Backup\nnot a link"
    sub := tunnel.ParseSubscription(links)
    println "servers:", sub.nodes.len(), "skipped:", sub.skipped

    mut options := vpn.Options {}
    options.fragment = true
    options.use_doh = true
    options.resolver = doh.NewResolver(doh.DefaultServers())
    vpn.RegisterWith(options)

    cfg := tunnel.ConfigFromNodes(sub.nodes, 18080)
    errors := tunnel.ValidateConfig(cfg)
    println "config problems:", errors.len()

    stats := tunnel.NewStats()
    listeners := vpn.StartAll(cfg, stats)
    println "listening:", vpn.Ok(listeners[0])
    vpn.Stop(listeners[0])
end
Outputservers: 2 skipped: 1 config problems: 0 listening: true

The example has made up servers (example.org) and only shows building and listening; put your own link in, save it to a file and run it:

salam run proxy.salam

A few honest points before you use it:

  • This is a tool for privacy and for reaching information. It does not make you anonymous and it does not promise that you will not be seen: a network that can recognize a proxy from its TLS fingerprint, its traffic pattern or active probing can block it.
  • Fragmenting and DoH are small measures. Test both on the network you are really on, and do not rely on one trick alone.
  • Only work with servers and links you know. The operator of any proxy can see where you connect, and a share link is itself a password or key; do not publish it.
  • Before you run a service for other people, or use one, learn the laws and rules of the country and the network you are in. The responsibility for how you use it is yours.
  • The limits of each protocol (not tested against a real server, no ML-KEM, the server clock for VMess and so on) are on the page of that package.

Streams and relays

Everything above sits on Stream. Wrap a TCP, TLS or WebSocket connection with WrapTcp, WrapTls or WrapWebSocket, or make two in memory ends with NewPipe, and the rest of the code is the same. Relay copies both ways until one side ends; RelayWith adds counters and an idle limit and is what ForwardHandler uses. Helpers such as ReadFull, ReadUntil and WriteAll deal with short reads and writes.

In memory pipes make protocol code easy to test without a socket: write the client's bytes to one end, run the handshake on the other, and compare the reply.

Routing rules

A Router holds ordered Rule values. A rule has destination conditions (exact host, suffix, keyword, regular expression, CIDR) and port conditions; a target matches when it passes one destination condition and one port condition (each only if the rule has some). The first matching rule gives its tag; when none match, the default tag is used. Matching ignores case and a trailing dot, and a suffix such as ir matches a.b.ir but not evilir.

Rules come from code, from ParseRules or LoadRules (the Clash like line format shown there), or from the routing part of a v2ray config through BuildRouter. A rule that names no condition never matches. ParseRules keeps the good lines and lists the bad ones in errors with their line numbers, so a typo does not silently drop a rule.

# one rule per line: KIND,value,tag
DOMAIN-SUFFIX,ir,direct
DOMAIN-KEYWORD,tracker,block
IP-CIDR,203.0.113.0/24,direct
GEOIP,PRIVATE,direct
DST-PORT,25,block
FINAL,proxy

GEOIP understands only PRIVATE. There is no country database in the package, which is why Iranian destinations are recognised by their domain suffix and a short list of well known domains rather than by IP ranges.

What to know

  • This is a proxy toolkit, not a VPN with a virtual interface.
  • Streams are TCP. IPv6 is parsed, routed, dialed and accepted.
  • A failed dial returns a closed stream. Check Ok.
  • Outbound protocols in a config need a registered maker, otherwise the outbound refuses traffic.
  • A Stats and an IdleTimer must be freed. A TcpInbound stops with Stop; connections already relayed carry on to their end.
  • TcpInbound serves each connection on a pool thread. The default handshake limit is 15 seconds, so a client that connects and says nothing is dropped.
  • No logging happens by itself. Add your own where it is safe to do so.

Types

Pending

interface Pending
A connection that has been accepted but not yet opened. Opening does the TLS or WebSocket upgrade on a worker thread, so a slow client cannot stall the accept loop.
net-tunnel-Inbound.salam
import net.tcp
import net.tunnel
import sync

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

struct LineProtocol:
    pub unused: int = 0

    pub func Name(): str:
        ret "line"
    end

    pub func Handshake(s: dyn tunnel.Stream): tunnel.Session:
        line := tunnel.ReadUntil(s, "\n", 200)
        if not line.starts_with("CONNECT "):
            ret tunnel.Session { error = "bad request" }
        end
        target := tunnel.ParseAddr(line.substr(8, line.len() - 9))
        if not target.valid:
            ret tunnel.Session { error = "bad address" }
        end
        ret tunnel.Session { ok = true, target = target, protocol = "line" }
    end

    pub func Reply(s: dyn tunnel.Stream, _sess: tunnel.Session, ok: bool):
        if ok:
            s.Write("OK\n")
        else:
            s.Write("ERR\n")
        end
    end
end

func echo_server():
    srv := tcp.Bind(0)
    echo_port = tcp.Port(srv)
    sync.Done(ready)
    until true:
        c := tcp.Accept(srv)
        tcp.Write(c, "echo:" + tcp.Read(c, 100))
        tcp.Close(c)
    end
end

func ask(port: int, request: str): str:
    c := tcp.Dial("127.0.0.1", port)
    tcp.Write(c, request)
    answer := tcp.Read(c, 100)
    rest := tcp.Read(c, 100)
    tcp.Close(c)
    ret answer + rest
end

func main:
    sync.Add(ready, 1 as i64)
    _t := spawn(echo_server)
    sync.Wait(ready)
    proto := LineProtocol {}
    forward := tunnel.NewForwarder(tunnel.Direct(), proto)
    inbound := tunnel.ListenTcp(0)
    println inbound.Ok()
    tunnel.ServeBackground(inbound, proto, forward)

    c := tcp.Dial("127.0.0.1", inbound.port)
    tcp.Write(c, "CONNECT 127.0.0.1:" + echo_port + "\n")
    println tcp.Read(c, 100)
    tcp.Write(c, "hello")
    println tcp.Read(c, 100)
    tcp.Close(c)

    println ask(inbound.port, "CONNECT 127.0.0.1:" + tcp.FreePort() + "\n")
    println ask(inbound.port, "GET / HTTP/1.1\r\n\r\n").len()
    inbound.Stop()
    println forward.stats.Total(), forward.stats.Failed()
end
Outputtrue OK echo:hello ERR 0 2 1

SourceTable

struct SourceTable
Counts the handshakes in progress per source address, to cap what one address can hold.
NameTypeDescription
corevoid*The shared state.

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

Acceptor

interface Acceptor
Something that accepts connections. TcpAcceptor is the plain one; net.vpn has one for TLS, WebSocket and HTTP upgrade.

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

ReadyConn

struct ReadyConn
A pending connection that needs no upgrade.
NameTypeDescription
streamdyn StreamThe stream.
okbool = falseTrue when the accept worked.
remotestr = ""The address of the client.

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

Valid

func Valid(): bool
True when the accept worked.

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

Open

func Open(): dyn Stream
Returns the stream.

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

Abort

func Abort()
Closes the stream.

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

Remote

func Remote(): str
The address of the client.

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

TcpAcceptor

struct TcpAcceptor
Accepts plain TCP connections.
NameTypeDescription
servertcp.ServerThe listening socket.

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

Accept

func Accept(): dyn Pending
Waits for the next TCP connection.

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

Port

func Port(): int
The port in use.

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

Ok

func Ok(): bool
False when listening failed.

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

Close

func Close()
Stops listening.

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

AcceptServer

struct AcceptServer
An accept loop with a worker pool. It runs any Acceptor, hands each connection to an InboundProtocol and then to a Handler.
NameTypeDescription
acceptordyn AcceptorThe acceptor.
hoststr = "127.0.0.1"The address it listens on.
runningatomic.FlagCleared by Stop.
workerspool.PoolThe pool that serves connections.
handshake_msint = 8000How long a client has to finish the handshake. The default is 8000.
per_sourceint = 0How many connections of one address may be in their handshake at once. 0 means no limit.
sourcesSourceTableThe handshakes in progress per address.

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

Ok

func Ok(): bool
False when listening failed.

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

Port

func Port(): int
The port in use.

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

Stop

func Stop()
Stops accepting and closes the listener.

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

DestPolicy

struct DestPolicy
Which destinations a server may reach. The default refuses loopback, private (RFC 1918), carrier grade NAT, link local and metadata (169.254.169.254), unique local IPv6, multicast, broadcast, unspecified and IPv4-mapped forms of these.
NameTypeDescription
allow_privatebool = falseAllows private and loopback targets, but only when loopback_only is true (set by PolicyForBind).
loopback_onlybool = falseTrue when the listener is bound to loopback only.
allow_cidrsVectorRanges that stay reachable even if special.
deny_cidrsVectorRanges that are always refused; they win over every allow entry.
allow_suffixesVectorDomain suffixes that stay reachable.
deny_suffixesVectorDomain suffixes that are always refused.
resolverdyn HostResolverThe resolver used to look names up before the check. The system resolver when none is set.
has_resolverbool = falseTrue when resolver is set.

Plan

struct Plan
The verdict of a policy for one target: allowed or not, and the checked addresses to dial.
NameTypeDescription
allowedbool = falseTrue when the target may be reached.
reasonstr = ""Why a target was refused.
ipsVectorThe checked addresses; dial these, never the name again.
resolvedbool = falseTrue when the name was resolved.

GuardResult

struct GuardResult
The outcome of a guarded dial.
NameTypeDescription
streamdyn StreamThe connected stream, or a closed one.
deniedbool = falseTrue when the policy refused the target.
reasonstr = ""Why it was refused.

GuardDialer

struct GuardDialer
A dialer that applies a destination policy: it resolves names once, checks every answer and connects to the checked address, so a name cannot change between the check and the dial.
NameTypeDescription
innerdyn DialerThe dialer used for the connection.
policyDestPolicyThe destination policy.
routerRouterDialerA router dialer used instead of inner.
has_routerbool = falseTrue when router is in use.

DialChecked

func DialChecked(target: Addr): GuardResult
Dials and reports whether the policy refused the target.

Dial

func Dial(target: Addr): dyn Stream
Dials under the policy; a refused target gives a closed stream.

Name

func Name(): str
The name of the dialer.

PipeStream

struct PipeStream
One end of an in memory pipe made by NewPipe. What one end writes the other reads. Good for tests and for joining two pieces of code without a socket.
NameTypeDescription
rx_RingThe buffer this end reads from.
tx_RingThe buffer this end writes to.
deadlineint = 0The timeout in milliseconds.
livebool = falseFalse once the stream has been closed.
net-tunnel-Stream.salam
import net.tunnel
import mem

func main:
    pair := tunnel.NewPipe()
    a := tunnel.PipeA(pair)
    b := tunnel.PipeB(pair)

    println tunnel.WriteAll(a, "HELLO world\nsecond line\n")
    println tunnel.ReadByte(b)
    println tunnel.ReadUntil(b, "\n", 100).len()
    println tunnel.ReadUntil(b, "\n", 100)

    a.Write("12345678")
    buf := mem.Allocate(8 as u64)
    println tunnel.ReadFull(b, buf, 8), (buf as u8*)[0], (buf as u8*)[7]
    mem.Free(buf)

    a.WriteBytes("abc" as void*, 3)
    w := tunnel.WithPrefix(b, "<")
    println w.Read(4)
    println w.Read(10)

    b.SetDeadline(50)
    println "nothing to read: [" + b.Read(10) + "]"

    println a.Ok(), b.Ok()
    a.Close()
    println a.Ok(), b.Read(10).len()
    dead := tunnel.ClosedStream {}
    println dead.Ok(), dead.Write("x") > 0, dead.Read(10).len()
end
Outputtrue 72 11 second line true 49 56 < abc nothing to read: [] true true true 0 false false 0

Read

func Read(max: int): str
Reads text from the stream; see Read.

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

ReadBytes

func ReadBytes(buf: void*, max: int): int
Reads bytes into a buffer; see ReadBytes.

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

Write

func Write(data: str): int
Writes text; see Write.

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

WriteBytes

func WriteBytes(ptr: void*, n: int): int
Writes bytes; see WriteBytes.

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

SetDeadline

func SetDeadline(millis: int)
Sets the read and write timeout in milliseconds.

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

Shutdown

func Shutdown()
Stops both directions; see Shutdown.

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

CloseWrite

func CloseWrite()
Ends the writing side of the pipe. The other end reads the end of the stream.

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

Close

func Close()
Closes the stream.

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

Ok

func Ok(): bool
True while the stream is usable.

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

PipePair

struct PipePair
The two ends of a pipe. Take them with PipeA and PipeB.
NameTypeDescription
aPipeStreamThe first end.
bPipeStreamThe second end.

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

PrefixStream

struct PrefixStream
Wraps a stream and hands out some bytes first, before anything the stream itself delivers. MixedProtocol uses it to put back the byte it peeked at.
NameTypeDescription
innerdyn StreamThe wrapped stream.
prefixstr = ""The bytes to give first.
offint = 0How much of the prefix has been given out already.

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

Read

func Read(max: int): str
Reads text from the stream; see Read.

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

ReadBytes

func ReadBytes(buf: void*, max: int): int
Reads bytes into a buffer; see ReadBytes.

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

Write

func Write(data: str): int
Writes text; see Write.

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

WriteBytes

func WriteBytes(ptr: void*, n: int): int
Writes bytes; see WriteBytes.

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

SetDeadline

func SetDeadline(millis: int)
Sets the read and write timeout in milliseconds.

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

Shutdown

func Shutdown()
Stops both directions; see Shutdown.

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

CloseWrite

func CloseWrite()
Passes the half close to the stream inside.

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

Close

func Close()
Closes the stream.

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

Ok

func Ok(): bool
True while the stream is usable.

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

RouterDialer

struct RouterDialer
A dialer that chooses another dialer by rules: it asks the router for a tag and dials with the dialer registered under that tag. Use it as the dialer of a ForwardHandler.
NameTypeDescription
routerRouterThe router that picks tags.
tagsVectorThe registered tags, in order.
dialersVectorThe dialers, one for each tag.
last_tagstr = ""The tag chosen for the most recent dial, handy in logs and tests.
net-tunnel-RouterDialer.salam
import net.tcp
import net.tunnel
import sync

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

func echo_once():
    srv := tcp.Bind(0)
    port = tcp.Port(srv)
    sync.Done(ready)
    c := tcp.Accept(srv)
    tcp.Write(c, "echo:" + tcp.Read(c, 64))
    tcp.Close(c)
end

func main:
    sync.Add(ready, 1 as i64)
    t := spawn(echo_once)
    sync.Wait(ready)
    mut router := tunnel.NewRouter("direct")
    mut blocked := tunnel.NewRule("block")
    tunnel.RuleSuffix(blocked, "blocked.test")
    tunnel.RouterAdd(router, blocked)

    mut rd := tunnel.NewRouterDialer(router)
    tunnel.RouterDialerAdd(rd, "direct", tunnel.Direct())
    tunnel.RouterDialerAdd(rd, "block", tunnel.Block())
    println rd.Name(), rd.tags.len(), rd.dialers.len()

    s := rd.Dial(tunnel.NewAddr("127.0.0.1", port))
    println rd.last_tag, s.Ok()
    s.Write("hi")
    println s.Read(100)
    s.Close()
    join(t)
    println rd.Dial(tunnel.NewAddr("ads.blocked.test", 80)).Ok(), rd.last_tag
    println rd.Pick("direct").Name(), rd.Pick("nothing").Name()
end
Outputrouter 2 2 direct true echo:hi false block direct block

Pick

func Pick(tag: str): dyn Dialer
Returns the dialer registered under a tag. An unknown tag gives a blocking dialer.

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

Dial

func Dial(target: Addr): dyn Stream
Asks the router for the target's tag, picks that dialer and dials with it.

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

Name

func Name(): str
Returns router.

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

Session

struct Session
The outcome of an inbound handshake: whether it worked and what the client asked for.
NameTypeDescription
okbool = falseTrue when the handshake succeeded and the target is known.
targetAddrThe address the client wants to reach.
protocolstr = ""Which protocol the client spoke, such as socks5 or http.
userstr = ""The authenticated user name, empty when there was none.
commandint = 1CMD_CONNECT or CMD_UDP.
initialstr = ""Bytes the client sent after the request that must be forwarded to the target first, such as the body of a plain HTTP request.
initial_datavoid*The same as initial for bytes that may contain zeros, such as the first payload after a Trojan request: a block of initial_size bytes that the forwarder writes to the target first and frees.
initial_sizeint = 0The number of bytes in initial_data; zero when the session uses initial or has none.
errorstr = ""Why the handshake failed, for logs.
wrappedbool = falseTrue when the protocol replaced the client stream with its own (for example an encrypted one). The handler must then use stream instead of the stream it was given.
streamdyn StreamThe replacement stream, valid only when wrapped is true.
refusedbool = falseTrue when the policy refused the target, so the inbound can answer with not allowed or 403.

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

InboundProtocol

interface InboundProtocol
The server side of a proxy protocol: it reads a client's request from a stream and answers it. SOCKS, HTTP and Trojan inbounds implement it.

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

Handler

interface Handler
Takes a client stream and its Session after a successful handshake and serves it. ForwardHandler is the usual one.

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

NullProtocol

struct NullProtocol
An inbound protocol that refuses everyone. It is the default value of a field.
NameTypeDescription
unusedint = 0Not used.
net-tunnel-Mixed.salam
import net.tunnel

func main:
    println tunnel.DetectMixed(5) == tunnel.MIXED_SOCKS5, tunnel.DetectMixed(4) == tunnel.MIXED_SOCKS4
    println tunnel.DetectMixed(71) == tunnel.MIXED_HTTP, tunnel.DetectMixed(22) == tunnel.MIXED_UNKNOWN
    println tunnel.ByteString(65), tunnel.ByteString(65).len()

    mut mixed := tunnel.NewMixed()
    println mixed.Name(), mixed.has_socks5, mixed.has_http
    tunnel.MixedSetSocks5(mixed, tunnel.NullProtocol {})
    tunnel.MixedSetSocks4(mixed, tunnel.NullProtocol {})
    tunnel.MixedSetHttp(mixed, tunnel.NullProtocol {})
    println mixed.has_socks5, mixed.has_socks4, mixed.has_http

    pair := tunnel.NewPipe()
    pair.a.Write("\x05\x01\x00")
    sess := mixed.Handshake(tunnel.PipeB(pair))
    println sess.ok, sess.protocol, sess.error
    println tunnel.CMD_CONNECT, tunnel.CMD_UDP
end
Outputtrue true true true A 1 mixed false false true true true false socks5 no protocol 1 3

Name

func Name(): str
Returns null.

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

Handshake

func Handshake(_s: dyn Stream): Session
Always fails.

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

Reply

func Reply(_s: dyn Stream, _sess: Session, _ok: bool)
Does nothing.

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

MixedProtocol

struct MixedProtocol
One port for SOCKS4, SOCKS5 and HTTP proxy at once: it looks at the first byte of each connection and hands it to the right protocol. Build it with NewMixed.
NameTypeDescription
socks4dyn InboundProtocolThe SOCKS4 inbound, when set.
socks5dyn InboundProtocolThe SOCKS5 inbound, when set.
httpdyn InboundProtocolThe HTTP proxy inbound, when set.
has_socks4bool = falseTrue after MixedSetSocks4.
has_socks5bool = falseTrue after MixedSetSocks5.
has_httpbool = falseTrue after MixedSetHttp.

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

Name

func Name(): str
Returns mixed.

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

Handshake

func Handshake(s: dyn Stream): Session
Reads one byte, picks the protocol with DetectMixed, gives it the byte back and lets it handshake. A protocol that was not set is refused.

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

Reply

func Reply(s: dyn Stream, sess: Session, ok: bool)
Answers in the protocol the client used.

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

ForwardHandler

struct ForwardHandler
The standard Handler: dials the target with its dialer, answers the client, forwards any initial bytes and then relays both directions until the connection ends.
NameTypeDescription
dialerdyn DialerThe dialer that reaches the target. Use a RouterDialer here to route by rules.
protodyn InboundProtocolThe inbound protocol, used to answer the client.
statsStatsCounters for connections and bytes.
idle_msint = 300000Close a relayed connection after this many milliseconds without traffic. The default is 300000; 0 means never.
guardGuardDialerThe guarded dialer used when has_guard is true.
has_guardbool = falseTrue when the forwarder dials through guard.

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

Handle

func Handle(s: dyn Stream, sess: Session)
Serves one client: dial, reply, relay, update the counters.

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

TcpInbound

struct TcpInbound
A TCP listener with a worker pool. Start it with ServeBackground or ServeForever.
NameTypeDescription
portint = 0The port it listens on. When you asked for port 0 this is the free port the system picked.
hoststr = ""The address it listens on; used to wake the accept loop when stopping.
listenertcp.ServerThe underlying TCP listener.
runningatomic.FlagA flag that Stop clears.
workerspool.PoolThe pool that serves connections. Each client gets a task.
handshake_msint = 8000How long a client has to finish its handshake, in milliseconds. The default is 8000.
per_sourceint = 0How many connections of one address may be in their handshake at once. 0 means no limit.
sourcesSourceTableThe handshakes in progress per address.

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

Ok

func Ok(): bool
True when the port was bound.

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

Serve

func Serve(proto: dyn InboundProtocol, h: dyn Handler)
Accepts connections in a loop, handshakes each with the protocol and gives the successful ones to the handler. Blocks until Stop.

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

Stop

func Stop()
Stops the accept loop and closes the listener. Connections already being relayed carry on until they end.

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

Dialer

interface Dialer
Something that opens an outgoing connection to a target and returns it as a Stream. Direct, blocking, proxied and routed dialers all implement it.
net-tunnel-Dialers.salam
import net.tcp
import net.tunnel
import sync

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

func echo_once():
    srv := tcp.Bind(0)
    port = tcp.Port(srv)
    sync.Done(ready)
    c := tcp.Accept(srv)
    data := tcp.Read(c, 64)
    tcp.Write(c, "echo:" + data)
    tcp.Close(c)
end

func main:
    sync.Add(ready, 1 as i64)
    t := spawn(echo_once)
    sync.Wait(ready)
    target := tunnel.NewAddr("127.0.0.1", port)

    blocked := tunnel.Block()
    println blocked.Name(), blocked.Dial(target).Ok()

    mut backup := tunnel.NewFallback()
    tunnel.FallbackAdd(backup, tunnel.Block())
    tunnel.FallbackAdd(backup, tunnel.Direct())
    println backup.Name()
    s := backup.Dial(target)
    println s.Ok()
    s.Write("hello")
    println s.Read(100)
    s.Close()
    join(t)

    direct := tunnel.DirectDialer { timeout_ms = 500 }
    println direct.Name(), direct.Dial(tunnel.NewAddr("127.0.0.1", tcp.FreePort())).Ok()
    stream := tunnel.DialTcp("127.0.0.1", tcp.FreePort(), 500)
    println stream.Ok()
end
Outputblock false fallback true echo:hello direct false false

Connector

interface Connector
One hop of a proxy chain: it names the server to reach and performs that proxy's handshake over an already open stream. SOCKS, HTTP and Trojan connectors implement it.
net-tunnel-Chain.salam
import net.tcp
import net.tunnel
import sync

mut hop_port := 0
mut hop_log := ""
mut ready := sync.NewWaitGroup()

struct LineHop:
    pub host: str = ""
    pub port: int = 0

    pub func Server(): tunnel.Addr:
        ret tunnel.NewAddr(this.host, this.port)
    end

    pub func Handshake(s: dyn tunnel.Stream, target: tunnel.Addr): bool:
        s.Write("HOP " + target.String() + "\n")
        ret tunnel.ReadUntil(s, "\n", 50) == "OK\n"
    end

    pub func Name(): str:
        ret "linehop"
    end
end

func hop_server():
    srv := tcp.Bind(0)
    hop_port = tcp.Port(srv)
    sync.Done(ready)
    c := tcp.Accept(srv)
    s := tunnel.WrapTcp(c)
    hop_log = tunnel.ReadUntil(s, "\n", 200)
    s.Write("OK\n")
    s.Write("end:" + s.Read(100))
    s.Close()
end

func main:
    sync.Add(ready, 1 as i64)
    t := spawn(hop_server)
    sync.Wait(ready)
    hop := LineHop { host = "127.0.0.1", port = hop_port }

    mut chain := tunnel.NewChain(tunnel.Direct())
    tunnel.ChainAdd(chain, hop)
    println chain.Name()
    s := chain.Dial(tunnel.NewAddr("service.internal", 8080))
    println s.Ok()
    s.Write("payload")
    println s.Read(100)
    s.Close()
    join(t)
    print hop_log

    via := tunnel.Via(tunnel.Direct(), hop)
    println via.Name()
end
Outputchain>linehop true end:payload HOP service.internal:8080 linehop

DirectDialer

struct DirectDialer
Connects straight to the target over TCP.
NameTypeDescription
timeout_msint = 10000How long a connect may take, in milliseconds. The default is 10000.
resolverdyn HostResolverOptional resolver for domain targets, for example one backed by DNS over HTTPS.
has_resolverbool = falseTrue to use the resolver. Each address is tried in turn within the time limit.
prefer_v6bool = falseTry IPv6 addresses before IPv4 ones.

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

Dial

func Dial(target: Addr): dyn Stream
Opens a TCP connection to the target and wraps it as a stream.

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

Name

func Name(): str
Returns direct.

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

BlockDialer

struct BlockDialer
A dialer that refuses everything. It is what a blackhole outbound becomes.
NameTypeDescription
refusedint = 0How many connections it has refused so far.

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

Dial

func Dial(_target: Addr): dyn Stream
Counts the attempt and returns a closed stream.

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

Name

func Name(): str
Returns block.

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

ProxyDialer

struct ProxyDialer
Dials through one proxy hop: it connects to the hop's server with the upstream dialer and then runs the hop's handshake.
NameTypeDescription
upstreamdyn DialerThe dialer used to reach the hop's server.
hopdyn ConnectorThe proxy hop to go through.

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

Dial

func Dial(target: Addr): dyn Stream
Reaches the hop, performs its handshake for the target and returns the stream, or a closed stream when any step fails.

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

Name

func Name(): str
A label made from the hop's name.

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

NullConnector

struct NullConnector
A connector that does nothing. It is only the default value of a field.
NameTypeDescription
unusedint = 0Not used. A struct needs at least one field.

Server

func Server(): Addr
Returns an empty address.

Handshake

func Handshake(_s: dyn Stream, _target: Addr): bool
Always returns false.

Name

func Name(): str
Returns null.

Chain

struct Chain
A dialer that goes through several proxy hops in order, starting from a base dialer. Build it with NewChain and ChainAdd.
NameTypeDescription
basedyn DialerThe dialer that reaches the first hop.
hopsVectorThe hops in the order they are used.

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

Dial

func Dial(target: Addr): dyn Stream
Connects to the first hop, asks it to connect to the second, and so on until the target. The stream it returns talks to the target through every hop.

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

Name

func Name(): str
A label such as chain>http>socks5.

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

Fallback

struct Fallback
A dialer that tries several dialers in order and returns the first stream that works.
NameTypeDescription
itemsVectorThe dialers to try, in order.

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

Dial

func Dial(target: Addr): dyn Stream
Tries each dialer and returns the first open stream, or a closed stream when all fail.

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

Name

func Name(): str
A label made from the names of the dialers.

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

Addr

struct Addr
A target to connect to: a host (name or IP) and a port, with the kind of host and a validity flag. ParseAddr and NewAddr fill it in.
NameTypeDescription
hoststr = ""The host name or IP text, without brackets for IPv6.
portint = 0The port, 0 to 65535.
kindint = 3ADDR_IPV4, ADDR_DOMAIN or ADDR_IPV6. The numbers are the ones the SOCKS5 address type uses.
validbool = falseFalse when parsing failed. Always check it before using the address.
sizeint = 0Filled in by the decoders: how many bytes the encoded address took on the wire.
net-tunnel-Addr.salam
import net.tunnel

func main:
    a := tunnel.ParseAddr("example.com:443")
    println a.valid, a.kind == tunnel.ADDR_DOMAIN, a.host, a.port, a.String()
    b := tunnel.ParseAddr("[2001:db8::5]:8080")
    println b.valid, b.kind == tunnel.ADDR_IPV6, b.String()
    println tunnel.ParseAddr("example.com").valid, tunnel.ParseAddr("a:99999").valid
    println tunnel.IsIP("10.1.1.1"), tunnel.IsIP("example.com")
    ip := tunnel.ParseIPv4("192.168.1.20")
    println ip.len(), tunnel.FormatIP(ip)
    println tunnel.FormatIP(tunnel.ParseIPv6("2001:0db8:0000:0000:0000:0000:0000:0001"))
    println tunnel.FormatIP(tunnel.ParseIP("::ffff:1.2.3.4"))
    n := tunnel.NewAddr("1.2.3.4", 80)
    println n.kind == tunnel.ADDR_IPV4, n.String()
end
Outputtrue true example.com 443 example.com:443 true true [2001:db8::5]:8080 false false true false 4 192.168.1.20 2001:db8::1 ::ffff:102:304 true 1.2.3.4:80

String

func String(): str
Returns the address as host:port, with brackets around an IPv6 host.

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

DomainSet

struct DomainSet
A set of domains: exact names, suffixes, keywords and regular expressions. Lookups on exact names and suffixes are hashed, so a list of a hundred thousand names stays fast.
NameTypeDescription
fullHashMapExact names.
suffixHashMapSuffixes; a name matches itself and every sub domain.
keywordsVectorWords that may appear anywhere in a name.
regexesVectorRegular expressions.
countint = 0How many entries were added.
net-tunnel-Router.salam
import net.tunnel

func main:
    rt := tunnel.IranRouter("direct", "proxy")
    each host in ["divar.ir", "WWW.Digikala.com", "ایران.ir", "notaparat.com", "evilir", "google.com", "192.168.1.9", "localhost"]:
        println host, rt.MatchHost(host, 443)
    end

    mut custom := tunnel.NewRouter("proxy")
    mut mail := tunnel.NewRule("block")
    tunnel.RulePort(mail, "25,465,587")
    tunnel.RouterAdd(custom, mail)
    mut ads := tunnel.NewRule("block")
    tunnel.RuleKeyword(ads, "tracker")
    tunnel.RuleRegex(ads, "^ads[0-9]+\\.example\\.com$")
    tunnel.RouterAdd(custom, ads)
    mut home := tunnel.NewRule("direct")
    tunnel.RuleSuffix(home, "example.org")
    tunnel.RuleDomain(home, "printer.lan")
    tunnel.RuleCidr(home, "203.0.113.0/24")
    tunnel.RouterAdd(custom, home)
    mut lan := tunnel.NewRule("direct")
    tunnel.RulePrivate(lan)
    tunnel.RouterAdd(custom, lan)

    println custom.MatchHost("smtp.example.com", 25)
    println custom.MatchHost("my-tracker.net", 443), custom.MatchHost("ads42.example.com", 443)
    println custom.MatchHost("a.b.example.org", 443), custom.MatchHost("printer.lan", 80)
    println custom.MatchHost("203.0.113.77", 80), custom.MatchHost("10.0.0.5", 22), custom.MatchHost("8.8.8.8", 53)
    println custom.Match(tunnel.ParseAddr("203.0.113.1:8080"))

    println tunnel.ParseCidr("10.0.0.0/8").prefix, tunnel.ParseCidr("nonsense").kind
    mut r := tunnel.NewRule("x")
    println tunnel.RuleDomainSpec(r, "full:a.example.com"), tunnel.RuleIpSpec(r, "geoip:private"), tunnel.RuleCidr(r, "1.2.3.4/40")
    println tunnel.RuleMatches(r, tunnel.ParseAddr("a.example.com:80")), tunnel.RuleMatches(r, tunnel.ParseAddr("b.example.com:80"))
end
Outputdivar.ir direct WWW.Digikala.com direct ایران.ir direct notaparat.com proxy evilir proxy google.com proxy 192.168.1.9 direct localhost direct block block block direct direct direct direct proxy direct 8 0 true true false true false

IpSet

struct IpSet
A set of IPv4 and IPv6 networks. A test costs at most one lookup per prefix length in use.
NameTypeDescription
v4HashMapIPv4 networks by prefix length and address.
v4_lensi64 = 0A bit mask of the IPv4 prefix lengths present.
v6HashMapIPv6 networks.
v6_lensVectorThe IPv6 prefix lengths present.
countint = 0How many networks were added.

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

Stats

struct Stats
Thread safe counters for a proxy: bytes up and down and connection counts. Make it with NewStats.
NameTypeDescription
upatomic.I64Bytes sent from the client side towards the target.
downatomic.I64Bytes sent from the target back to the client.
totalatomic.I64Connections handled in total.
activeatomic.I64Connections being relayed right now.
failedatomic.I64Connections whose target could not be reached.
net-tunnel-Relay.salam
import net.tunnel
import pool

func relay_job(a: dyn tunnel.Stream, b: dyn tunnel.Stream, st: tunnel.Stats):
    r := tunnel.RelayWith(a, b, st, 5000)
    println "relayed", r.up, r.down
end

func main:
    near := tunnel.NewPipe()
    far := tunnel.NewPipe()
    client := tunnel.PipeA(near)
    left := tunnel.PipeB(near)
    right := tunnel.PipeA(far)
    server := tunnel.PipeB(far)
    stats := tunnel.NewStats()

    workers := pool.NewElastic(1 as i64, 2 as i64, 2 as i64)
    pool.Submit(workers, (): relay_job(left, right, stats) end)

    client.Write("ping from the client")
    println server.Read(100)
    server.Write("pong from the server")
    println client.Read(100)
    client.Close()
    pool.Wait(workers)
    pool.Stop(workers)
    println stats.Summary()
    println stats.Up(), stats.Down()
    tunnel.FreeStats(stats)
end
Outputping from the client pong from the server relayed 20 20 up=20 down=20 total=0 active=0 failed=0 20 20

Up

func Up(): i64
Reads the up counter.

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

Down

func Down(): i64
Reads the down counter.

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

Total

func Total(): i64
Reads the total counter.

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

Active

func Active(): i64
Reads the active counter.

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

Failed

func Failed(): i64
Reads the failed counter.

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

Summary

func Summary(): str
Returns all counters as one line, up=... down=... total=... active=... failed=....

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

RelayResult

struct RelayResult
How many bytes Relay moved in each direction.
NameTypeDescription
upi64 = 0Bytes from the first stream to the second.
downi64 = 0Bytes from the second stream to the first.

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

IdleTimer

struct IdleTimer
Remembers when traffic last moved and says whether a connection has been quiet too long.
NameTypeDescription
lastatomic.I64The time of the last activity, in milliseconds.
limiti64 = 0How long it may be quiet, in milliseconds. 0 means no limit.
net-tunnel-Idle.salam
import net.tunnel
import sync

func main:
    timer := tunnel.NewIdleTimer(100 as i64)
    println timer.Expired()
    sync.SleepMs(160)
    println timer.Expired(), timer.IdleMs() >= 150
    timer.Touch()
    println timer.Expired()
    timer.Free()

    plain := tunnel.NewIdleTimer(0 as i64)
    sync.SleepMs(30)
    println "no limit:", plain.Expired()
    plain.Free()
end
Outputfalse true true false no limit: false

Touch

func Touch()
Records activity now.

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

IdleMs

func IdleMs(): i64
Milliseconds since the last activity.

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

Expired

func Expired(): bool
True when the limit is set and the idle time is over it.

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

Free

func Free()
Releases the timer.

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

Node

struct Node
One proxy server described by a share link: protocol, address, credentials and transport options. ParseLink fills it and NodeToLink writes it back.
NameTypeDescription
validbool = falseFalse when parsing failed; see error.
errorstr = ""Why parsing failed.
protocolstr = ""vmess, vless, trojan or ss.
remarkstr = ""The human name after the # in the link.
hoststr = ""The server host name or IP.
portint = 0The server port.
uuidstr = ""The user id of VMess and VLESS.
passwordstr = ""The password of Trojan and Shadowsocks.
methodstr = ""The cipher: the Shadowsocks method or the VMess security.
alter_idint = 0The VMess alter id, normally 0.
flowstr = ""The VLESS flow, such as xtls-rprx-vision.
encryptionstr = ""The VLESS encryption field, normally none.
networkstr = "tcp"The transport: tcp, ws, grpc and so on. The default is tcp.
header_typestr = ""The header obfuscation type of the transport.
tlsbool = falseTrue when the link asks for TLS.
securitystr = ""none, tls or reality.
snistr = ""The server name sent in the TLS handshake.
alpnstr = ""The ALPN list, comma separated.
fingerprintstr = ""The client fingerprint name, such as chrome.
pathstr = ""The WebSocket or HTTP path.
host_headerstr = ""The Host header for WebSocket and HTTP transports.
service_namestr = ""The gRPC service name.
grpc_modestr = ""multi for a gRPC link with mode=multi (or a vmess link with type multi), empty for gun.
public_keystr = ""The REALITY public key.
short_idstr = ""The REALITY short id.
spider_xstr = ""The REALITY spider path.
pluginstr = ""The Shadowsocks plugin string, if any.
allow_insecurebool = falseTrue when the link says to skip certificate checks. Treat this as a warning sign.
net-tunnel-Link.salam
import net.tunnel

func main:
    link := "vless://b831381d-6324-4d53-ad4f-8cda48b30811@203.0.113.9:443?encryption=none&security=tls&sni=cdn.example.com&type=ws&host=cdn.example.com&path=%2Fws#Berlin%201"
    n := tunnel.ParseLink(link)
    println n.valid, n.protocol, tunnel.NodeAddr(n).String(), n.network, n.security, n.sni, n.path
    println tunnel.NodeLabel(n)
    println tunnel.NodeToLink(n) == link

    println tunnel.ParseLink("vless://@host:1").valid, tunnel.ParseLink("http://x").valid
    println tunnel.ParseLink("ss://aes-128-gcm:secret@10.0.0.1:8388#ss").method

    text := link + "\n# a comment\ntrojan://pw@t.example.org:443#T\nnot a link\n"
    sub := tunnel.ParseSubscription(text)
    println sub.nodes.len(), sub.skipped, tunnel.NodeLabel(sub.nodes[1])

    println tunnel.PctEncode("a b/c"), tunnel.PctDecode("%D8%B3%D9%84%D8%A7%D9%85")
    println tunnel.B64EncodeUrl("subjects?_d>>"), tunnel.B64Decode("c2FsYW0")
end
Outputtrue vless 203.0.113.9:443 ws tls cdn.example.com /ws Berlin 1 true false false aes-128-gcm 2 1 T a%20b%2Fc سلام c3ViamVjdHM_X2Q-Pg salam

Subscription

struct Subscription
The nodes found in a subscription text, plus how many lines were not understood.
NameTypeDescription
nodesVectorThe valid nodes in order.
skippedint = 0How many non empty, non comment lines were not valid links.

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

Stream

interface Stream
A two way byte connection. Plain TCP, TLS, WebSocket and in memory pipes all implement it, so protocols and relays work on any of them.

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

TcpStream

struct TcpStream
A Stream over a plain TCP connection. WrapTcp makes one.
NameTypeDescription
conntcp.ConnThe TCP connection.
net-tunnel-Wrap.salam
import net.tcp
import net.tunnel
import net.websocket
import sync

mut tcp_port := 0
mut ws_port := 0
mut ready := sync.NewWaitGroup()

func tcp_server():
    srv := tcp.Bind(0)
    tcp_port = tcp.Port(srv)
    sync.Done(ready)
    c := tcp.Accept(srv)
    s := tunnel.WrapTcp(c)
    s.Write("tcp:" + s.Read(100))
    s.Close()
end

func ws_server():
    srv := websocket.Listen(0)
    ws_port = websocket.Port(srv)
    sync.Done(ready)
    c := websocket.Accept(srv)
    s := tunnel.WrapWebSocket(c)
    first := s.Read(5)
    rest := s.Read(100)
    s.Write("ws:" + first + rest)
    s.Close()
end

func main:
    sync.Add(ready, 2 as i64)
    t1 := spawn(tcp_server)
    t2 := spawn(ws_server)
    sync.Wait(ready)

    plain := tunnel.DialTcp("127.0.0.1", tcp_port, 2000)
    plain.Write("hello")
    println plain.Read(100)
    plain.Close()

    conn := websocket.Dial("127.0.0.1", ws_port, "/")
    stream := tunnel.WrapWebSocket(conn)
    tunnel.WriteAll(stream, "hello world")
    println tunnel.ReadByte(stream)
    println stream.Read(100)
    stream.Close()
    join(t1)
    join(t2)
end
Outputtcp:hello 119 s:hello world

Read

func Read(max: int): str
Reads text from the stream; see Read.

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

ReadBytes

func ReadBytes(buf: void*, max: int): int
Reads bytes into a buffer; see ReadBytes.

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

Write

func Write(data: str): int
Writes text; see Write.

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

WriteBytes

func WriteBytes(ptr: void*, n: int): int
Writes bytes; see WriteBytes.

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

SetDeadline

func SetDeadline(millis: int)
Sets the read and write timeout in milliseconds.

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

Shutdown

func Shutdown()
Stops both directions; see Shutdown.

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

CloseWrite

func CloseWrite()
Shuts down the sending side of the socket and keeps reading.

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

Close

func Close()
Closes the stream.

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

Ok

func Ok(): bool
True while the stream is usable.

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

TlsStream

struct TlsStream
A Stream over a TLS connection. WrapTls makes one.
NameTypeDescription
conntls.ConnThe TLS connection.

Read

func Read(max: int): str
Reads text from the stream; see Read.

ReadBytes

func ReadBytes(buf: void*, max: int): int
Reads bytes into a buffer; see ReadBytes.

Write

func Write(data: str): int
Writes text; see Write.

WriteBytes

func WriteBytes(ptr: void*, n: int): int
Writes bytes; see WriteBytes.

SetDeadline

func SetDeadline(millis: int)
Sets the read and write timeout in milliseconds.

Shutdown

func Shutdown()
Stops both directions; see Shutdown.

CloseWrite

func CloseWrite()
Sends close_notify and shuts down the sending side. Reads keep working.

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

Close

func Close()
Closes the stream.

Ok

func Ok(): bool
True while the stream is usable.

WsStream

struct WsStream
A Stream over a WebSocket connection. WrapWebSocket makes one.
NameTypeDescription
connwebsocket.ConnThe WebSocket connection.
pendingvoid*A message that was only partly read.
pending_lenint = 0The size of the partly read message.
pending_offint = 0How much of it has been read.
timeoutint = -1The timeout in milliseconds, or -1 for none.

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

Read

func Read(max: int): str
Reads text from the stream; see Read.

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

ReadBytes

func ReadBytes(buf: void*, max: int): int
Reads bytes into a buffer; see ReadBytes.

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

Write

func Write(data: str): int
Writes text; see Write.

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

WriteBytes

func WriteBytes(ptr: void*, n: int): int
Writes bytes; see WriteBytes.

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

SetDeadline

func SetDeadline(millis: int)
Sets the read and write timeout in milliseconds.

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

Shutdown

func Shutdown()
Stops both directions; see Shutdown.

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

CloseWrite

func CloseWrite()
WebSocket has no half close, so this shuts down the whole connection.

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

Close

func Close()
Closes the stream.

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

Ok

func Ok(): bool
True while the stream is usable.

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

ClosedStream

struct ClosedStream
A stream that is already closed: reads return nothing and writes fail. A failed dial returns this instead of nothing.
NameTypeDescription
unusedint = 0Not used.

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

Read

func Read(_max: int): str
Reads text from the stream; see Read.

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

ReadBytes

func ReadBytes(_buf: void*, _max: int): int
Reads bytes into a buffer; see ReadBytes.

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

Write

func Write(_data: str): int
Writes text; see Write.

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

WriteBytes

func WriteBytes(_ptr: void*, _n: int): int
Writes bytes; see WriteBytes.

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

SetDeadline

func SetDeadline(millis: int)
Sets the read and write timeout in milliseconds.

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

Shutdown

func Shutdown()
Stops both directions; see Shutdown.

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

CloseWrite

func CloseWrite()
Does nothing.

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

Close

func Close()
Closes the stream.

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

Ok

func Ok(): bool
True while the stream is usable.

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

InboundConfig

struct InboundConfig
One inbound of a v2ray style config: where to listen and which protocol to speak.
NameTypeDescription
tagstr = ""The name other parts of the config use for this inbound.
protocolstr = ""The protocol name, such as mixed, socks or http. It picks the registered InboundMaker.
listenstr = "127.0.0.1"The address to listen on. The default is 127.0.0.1.
portint = 0The port to listen on. A text port in the JSON is accepted too.
settingsstr = ""The raw JSON of the settings object, kept as text for the protocol to read.
stream_settingsstr = ""The raw JSON of streamSettings for an inbound, kept as text for the protocol to read.
net-tunnel-Config.salam
import net.tunnel

func main:
    text := `{
  "inbounds": [{"tag": "in", "listen": "127.0.0.1", "port": 10808, "protocol": "mixed"}],
  "outbounds": [
    {"tag": "proxy", "protocol": "trojan", "settings": {"servers": [{"address": "t.example.org", "port": 443, "password": "pw"}]}},
    {"tag": "direct", "protocol": "freedom"},
    {"tag": "block", "protocol": "blackhole"}
  ],
  "routing": {"defaultOutbound": "proxy", "rules": [
    {"type": "field", "domain": ["domain:ir"], "outboundTag": "direct"},
    {"type": "field", "ip": ["geoip:private"], "outboundTag": "direct"},
    {"type": "field", "port": "25", "outboundTag": "block"}
  ]}
}`
    c := tunnel.ParseConfig(text)
    println c.errors.len(), c.inbounds.len(), c.outbounds.len(), c.rules.len(), c.default_outbound
    println c.inbounds[0].port, c.outbounds[0].protocol, c.rules[2].port
    println "problems:", tunnel.ValidateConfig(c).len()

    rt := tunnel.BuildRouter(c)
    println rt.MatchHost("divar.ir", 443), rt.MatchHost("192.168.1.1", 80), rt.MatchHost("mail.example.com", 25), rt.MatchHost("example.com", 443)
    println tunnel.ParseConfig(tunnel.ConfigToJson(c)).outbounds.len()

    bad := tunnel.ParseConfig(`{"outbounds":[{"tag":"a","protocol":"freedom"},{"tag":"a","protocol":"freedom"}],"routing":{"rules":[{"outboundTag":"zzz","domain":["domain:x"]}]}}`)
    each e in tunnel.ValidateConfig(bad):
        println e
    end
    println tunnel.LoadConfig("/nonexistent/config.json").errors.len()

    node := tunnel.ParseLink("trojan://pw@t.example.org:443?security=tls&sni=t.example.org#T")
    out := tunnel.OutboundFromNode(node, "proxy")
    println out.tag, out.protocol, tunnel.NodeToLink(tunnel.NodeFromOutbound(out)) == tunnel.NodeToLink(node)
    mut nodes := Vector {} as Vector<tunnel.Node>
    nodes.push(node)
    gen := tunnel.ConfigFromNodes(nodes, 7890)
    println gen.inbounds[0].port, gen.outbounds.len(), gen.default_outbound, gen.rules.len()
end
Output0 1 3 3 proxy 10808 trojan 25 problems: 0 direct direct block proxy 3 duplicate outbound tag a rule 0 points to unknown outbound 'zzz' 1 proxy trojan false 7890 3 proxy-1 1

OutboundConfig

struct OutboundConfig
One outbound of a config: a tag, a protocol and its settings.
NameTypeDescription
tagstr = ""The name rules use to send traffic here.
protocolstr = ""The protocol name. freedom and blackhole are built in; others need a registered OutboundMaker.
settingsstr = ""The raw JSON of the settings object.
stream_settingsstr = ""The raw JSON of streamSettings: network, security and their options.

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

RuleConfig

struct RuleConfig
One routing rule of a config: conditions and the outbound tag to use when they match.
NameTypeDescription
domainVectorDomain conditions such as domain:ir, full:a.com, keyword:bank or regexp:....
ipVectorIP conditions: CIDR ranges, single addresses or geoip:private.
portstr = ""A port condition such as 25,465 or 8000-8100.
outbound_tagstr = ""The tag of the outbound that gets the traffic.

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

Config

struct Config
A whole v2ray style config: inbounds, outbounds and routing rules. ParseConfig and LoadConfig produce it.
NameTypeDescription
log_levelstr = "warning"The log level from the config. The default is warning.
inboundsVectorThe inbounds in file order.
outboundsVectorThe outbounds in file order.
rulesVectorThe routing rules in file order. The first match wins.
default_outboundstr = ""The tag used when no rule matches. Without one, the first outbound is used.
errorsVectorProblems found while parsing, as readable lines. Empty for a good config.

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

OutboundMaker

interface OutboundMaker
Something that can build a Dialer for one outbound protocol. Register it with RegisterOutbound so configs can use the protocol name.
net-tunnel-Registry.salam
import net.tunnel

struct LoopbackMaker:
    pub unused: int = 0

    pub func Protocol(): str:
        ret "loopback"
    end

    pub func Make(_cfg: tunnel.OutboundConfig): dyn tunnel.Dialer:
        ret tunnel.Direct()
    end
end

struct SilentInbound:
    pub unused: int = 0

    pub func Protocol(): str:
        ret "silent"
    end

    pub func Make(_cfg: tunnel.InboundConfig): dyn tunnel.InboundProtocol:
        ret tunnel.NullProtocol {}
    end
end

func main:
    println tunnel.HasOutbound("freedom"), tunnel.HasOutbound("loopback")
    tunnel.RegisterOutbound(LoopbackMaker {})
    tunnel.RegisterInbound(SilentInbound {})
    println tunnel.HasOutbound("loopback"), tunnel.HasInbound("silent")
    println tunnel.CreateOutbound(tunnel.OutboundConfig { protocol = "loopback" }).Name()
    println tunnel.CreateOutbound(tunnel.OutboundConfig { protocol = "freedom" }).Name()
    println tunnel.CreateOutbound(tunnel.OutboundConfig { protocol = "nobody-registered-this" }).Name()
    println tunnel.CreateInbound(tunnel.InboundConfig { protocol = "silent" }).Name()
    c := tunnel.ParseConfig(`{"outbounds":[{"tag":"lo","protocol":"loopback"},{"tag":"deny","protocol":"blackhole"}],"routing":{"defaultOutbound":"lo","rules":[{"domain":["full:blocked.test"],"outboundTag":"deny"}]}}`)
    rd := tunnel.BuildDialer(c)
    println rd.Pick("lo").Name(), rd.Pick("deny").Name(), rd.Pick("missing").Name()
    println rd.Dial(tunnel.NewAddr("blocked.test", 80)).Ok(), rd.last_tag
end
Outputtrue false true true direct direct block null direct block block false deny

InboundMaker

interface InboundMaker
Something that can build an InboundProtocol for one inbound protocol. Register it with RegisterInbound.

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

ConnLimits

struct ConnLimits
The limits of one server.
NameTypeDescription
activei64*Shared count of admitted connections.
max_connectionsint = 1024The cap on connections; 0 for none.
max_per_sourceint = 32Handshakes in progress allowed per source; 0 for none.
deadline_msint = 10000Total handshake time in milliseconds; 0 for none.
sourcesSourceTablePer source counts.

Admit

struct Admit
The admission of one connection.
NameTypeDescription
okbool = falseTrue when the connection was admitted.
hoststr = ""The source address.
ticketi64 = 0The deadline ticket.
countedbool = falseTrue when counted against the per source limit.
heldbool = falseTrue while a slot is held.
donebool = falseTrue once the handshake phase ended.

LookupResult

struct LookupResult
The addresses a resolver found for a host name.
NameTypeDescription
ipsVectorIPv4 and IPv6 address texts.

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

HostResolver

interface HostResolver
Something that turns a host name into addresses. net.vpn has one for net.doh; any lookup function can be wrapped.

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

NullResolver

struct NullResolver
A resolver that finds nothing.
NameTypeDescription
unusedint = 0Not used.

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

Lookup

func Lookup(_host: str): LookupResult
Always returns no addresses.

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

ResolveCache

struct ResolveCache
The answers a router remembers, so a name is looked up once per time limit.
NameTypeDescription
musync.MutexLock for the cache.
ipsHashMap>Addresses by host name.
expiresHashMapWhen each answer stops being valid, in milliseconds.
ttl_msi64 = 300000How long an answer is kept. The default is 300000.
maxint = 4096How many names to keep before the cache is emptied.
lookupsint = 0How many real lookups were made.

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

Matcher

struct Matcher
One condition of a rule. Fill it with the Rule... functions rather than by hand.
NameTypeDescription
kindint = 0One of the M_ constants.
valuestr = ""The text of the condition.
netVectorFor a CIDR: the network address as numbers.
prefixint = 0For a CIDR: the prefix length in bits.
loint = 0For a port range: the lowest port.
hiint = 0For a port range: the highest port.
dsetDomainSetFor a geosite matcher: the domain set.
isetIpSetFor a geoip matcher: the address set.

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

Rule

struct Rule
A tag plus destination and port conditions. A target matches when it satisfies a destination matcher (if any) and a port matcher (if any).
NameTypeDescription
tagstr = ""The outbound tag the rule picks.
destVectorDestination matchers: any one is enough.
portsVectorPort matchers: any one is enough.

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

Router

struct Router
An ordered list of rules and a default tag. The first rule that matches decides. A rule with no conditions never matches.
NameTypeDescription
rulesVectorThe rules in the order they are tried.
default_tagstr = "direct"The tag returned when no rule matches.
resolverdyn HostResolverUsed to turn a domain target into addresses so that CIDR and geoip rules can match it.
has_resolverbool = falseTrue after SetResolver.
cacheResolveCacheThe remembered answers of the resolver.

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

Resolve

func Resolve(host: str): Vector<Vector<int>>
Looks a name up through the resolver and the cache. Returns the addresses as numbers.

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

Match

func Match(target: Addr): str
Returns the tag for a target address.

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

MatchHost

func MatchHost(host: str, port: int): str
Returns the tag for a host text and a port. Case and a trailing dot of the host are ignored.

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

RuleSet

struct RuleSet
What ParseRules returns: the router and the list of lines it could not use.
NameTypeDescription
routerRouterThe router built from the good lines.
errorsVectorOne message per bad line, with its line number.
net-tunnel-Rules.salam
import net.tunnel

func main:
    text := "# send Iranian sites direct\nDOMAIN-SUFFIX,ir,direct\nDOMAIN-KEYWORD,tracker,block\nIP-CIDR,203.0.113.0/24,direct\nGEOIP,PRIVATE,direct\nDST-PORT,25,block\nthis line is broken\nFINAL,proxy\n"
    set := tunnel.ParseRules(text, "proxy")
    println "errors:", set.errors.len()
    each e in set.errors:
        println e
    end
    each host in ["shop.ir", "my-tracker.net", "203.0.113.5", "127.0.0.1", "example.com"]:
        println host, set.router.MatchHost(host, 443)
    end
    println set.router.MatchHost("example.com", 25)
    missing := tunnel.LoadRules("/nonexistent/rules.txt", "direct")
    println missing.errors.get(0)
end
Outputerrors: 1 line 7: expected KIND,VALUE,TAG shop.ir direct my-tracker.net block 203.0.113.5 direct 127.0.0.1 direct example.com proxy block cannot read /nonexistent/rules.txt

Constants and variables

Commands CMD_…

The values of command.
NameValue
CMD_CONNECT1
CMD_UDP3

Mixed protocol kinds MIXED_…

What DetectMixed returns.
NameValue
MIXED_UNKNOWN0
MIXED_HTTP1
MIXED_SOCKS44
MIXED_SOCKS55

Address kinds ADDR_…

The values of kind.
NameValue
ADDR_IPV41
ADDR_DOMAIN3
ADDR_IPV64

IRAN_DOMAINS

const IRAN_DOMAINS: str = "# bundled Iran direct list
domain:ir
domain:xn--mgba3a4f16a
domain:xn--mgba3a4fra1a
digikala.com
aparat.com
snapp.taxi
tapsi.cab
bale.ai
eitaa.com
telewebion.com
filimo.com
namava.ir
rubika.ir
soroush-app.ir
"
The built in Iran direct list as text: the .ir domains and a few large services.

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

DeadlineDirect

const DeadlineDirect: i32 = -2
Passed to SetDeadline of a stream that wraps a TLS connection, it means: read straight from the socket from now on (XTLS Vision direct copy). Other streams never get it.

Matcher kinds M_…

The values of kind.
NameValue
M_DOMAIN1
M_SUFFIX2
M_KEYWORD3
M_REGEX4
M_CIDR5
M_PORT6
M_GEOSITE7
M_GEOIP8

Functions

NewSourceTable

func NewSourceTable(): SourceTable
Makes an empty table.

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

SourceKey

func SourceKey(remote: str): str
The key a client address is counted under: IPv4 as is, an IPv4-mapped IPv6 address as its IPv4 form, any other IPv6 address as its /64 prefix.
net-tunnel-IPv6.salam
import net.tunnel

func main:
    println tunnel.SourceKey("::ffff:203.0.113.9"), tunnel.SourceKey("2001:db8:1:2::7")
    local := tunnel.ListenLocal6(0)
    println local.Ok()
    local.Stop()
    dual := tunnel.ListenTcpDual(0)
    println dual.Ok()
    dual.Stop()
    at := tunnel.ListenTcpAt("::1", 0)
    println at.Ok(), at.host
    at.Stop()
    inbound := tunnel.ListenTcpDualInbound(0)
    println inbound.Ok()
    inbound.Stop()
end

SourceEnter

func SourceEnter(t: SourceTable, remote: str, limit: int): bool
Counts one more handshake for the address. False when it already has the limit, so the connection should be dropped. A limit of 0 or an empty address always passes.

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

SourceLeave

func SourceLeave(t: SourceTable, remote: str)
Takes one handshake back off the count of the address.

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

NewAcceptServer

func NewAcceptServer(a: dyn Acceptor, host: str): AcceptServer
Makes an accept server around an acceptor. The host is only used to wake the loop on Stop.

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

ListenTcpOn

func ListenTcpOn(host: str, port: int): AcceptServer
Listens on one address. Use 127.0.0.1 for a local proxy, ::1 for IPv6 loopback, 0.0.0.0 for every IPv4 interface and :: for IPv4 and IPv6 together.

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

ListenTcpDual

func ListenTcpDual(port: int): AcceptServer
Listens on ::, so IPv4 and IPv6 clients are both accepted.

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

ListenLocal6

func ListenLocal6(port: int): AcceptServer
Listens on ::1 only. Port 0 picks a free port.

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

ListenLocal

func ListenLocal(port: int): AcceptServer
Listens on 127.0.0.1 only. Port 0 picks a free port.

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

StartAccepting

func StartAccepting(srv: AcceptServer, proto: dyn InboundProtocol, h: dyn Handler)
Starts the accept loop on its own thread.

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

NewDestPolicy

func NewDestPolicy(): DestPolicy
A policy with the safe defaults.

PolicyAllowPrivate

func PolicyAllowPrivate(p &: DestPolicy, enabled: bool)
Turns allow_private on or off.

PolicyAllowCidr

func PolicyAllowCidr(p &: DestPolicy, text: str): bool
Adds an allowed range; false for a bad range.

PolicyDenyCidr

func PolicyDenyCidr(p &: DestPolicy, text: str): bool
Adds a refused range; false for a bad range.

PolicyAllowSuffix

func PolicyAllowSuffix(p &: DestPolicy, suffix: str)
Adds an allowed domain suffix.

PolicyDenySuffix

func PolicyDenySuffix(p &: DestPolicy, suffix: str)
Adds a refused domain suffix.

PolicySetResolver

func PolicySetResolver(p &: DestPolicy, r: dyn HostResolver)
Sets the resolver used before the check, for example a DoH resolver.

IsLoopbackHost

func IsLoopbackHost(host: str): bool
True for localhost, 127.0.0.0/8 and ::1.

PolicyForBind

func PolicyForBind(p: DestPolicy, bind_host: str): DestPolicy
Returns the policy to use for a listener on a host: allow_private only counts when the host is loopback.

IsSpecialIP

func IsSpecialIP(b: Vector<int>): bool
True for the bytes of an address that must not be reached by default.

IsPrivateAddress

func IsPrivateAddress(ip: str): bool
True for a text address that must not be reached by default.

PolicyCheckBytes

func PolicyCheckBytes(p: DestPolicy, ip: Vector<int>): bool
Checks the bytes of an address against a policy.

PolicyCheckIP

func PolicyCheckIP(p: DestPolicy, ip: str): bool
Checks a text address against a policy.

PolicyCheckName

func PolicyCheckName(p: DestPolicy, host: str): bool
Checks a domain name against the suffix lists and the localhost rule.

PolicyPlan

func PolicyPlan(p: DestPolicy, a: Addr, resolve: bool): Plan
Checks a target and, when asked, resolves the name once and checks every answer. Dial ips from the result.

Guard

func Guard(inner: dyn Dialer, p: DestPolicy): GuardDialer
Wraps a dialer with a destination policy.

GuardRouter

func GuardRouter(rd: RouterDialer, p: DestPolicy): GuardDialer
Wraps a router dialer with a destination policy; names routed direct are pinned to the checked address.

NewPipe

func NewPipe(): PipePair
Makes a pipe with two ends.

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

PipeA

func PipeA(p: PipePair): dyn Stream
Returns the first end as a Stream.

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

PipeB

func PipeB(p: PipePair): dyn Stream
Returns the second end as a Stream.

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

WrapTcp

func WrapTcp(c: tcp.Conn): dyn Stream
Wraps a TCP connection as a Stream.

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

WrapTls

func WrapTls(c: tls.Conn): dyn Stream
Wraps a TLS connection as a Stream.

WrapWebSocket

func WrapWebSocket(c: websocket.Conn): dyn Stream
Wraps a WebSocket connection as a Stream: bytes written become binary messages, and incoming messages are read as a continuous byte stream.

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

DialTcp

func DialTcp(host: str, port: int, timeout_ms: int): dyn Stream
Connects over TCP with a timeout in milliseconds and returns the stream. Check Ok.

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

WithPrefix

func WithPrefix(s: dyn Stream, prefix: str): dyn Stream
Returns a stream that gives the prefix first and then whatever the inner stream gives.

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

ReadFull

func ReadFull(s: dyn Stream, buf: void*, n: int): bool
Reads exactly the given number of bytes into a buffer, looping over short reads. False when the stream ends first.

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

WriteAll

func WriteAll(s: dyn Stream, data: str): bool
Writes the whole text, looping over short writes. False on failure.

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

WriteAllBytes

func WriteAllBytes(s: dyn Stream, ptr: void*, n: int): bool
Writes the whole buffer. False on failure.

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

ReadByte

func ReadByte(s: dyn Stream): int
Reads one byte as a number 0 to 255, or -1 at the end of the stream.

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

ReadUntil

func ReadUntil(s: dyn Stream, delim: str, max: int): str
Reads until the delimiter text appears or the maximum is reached. The delimiter is included.

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

NewRouterDialer

func NewRouterDialer(router: Router): RouterDialer
Makes a router dialer with no dialers yet.

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

RouterDialerAdd

func RouterDialerAdd(rd &: RouterDialer, tag: str, d: dyn Dialer)
Registers a dialer under a tag.

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

BuildDialer

func BuildDialer(c: Config): RouterDialer
Builds a RouterDialer from a whole Config: its router from the rules, and one dialer for each outbound.

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

DetectMixed

func DetectMixed(first: int): int
Guesses the protocol from the first byte: 5 is SOCKS5, 4 is SOCKS4, a letter is HTTP, anything else is unknown.

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

ByteString

func ByteString(b: int): str
Makes a one byte text from a number 0 to 255.

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

NewMixed

func NewMixed(): MixedProtocol
Returns an empty MixedProtocol. Add protocols with the MixedSet functions.

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

MixedSetSocks5

func MixedSetSocks5(m &: MixedProtocol, p: dyn InboundProtocol)
Sets the SOCKS5 inbound of a mixed protocol.

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

MixedSetSocks4

func MixedSetSocks4(m &: MixedProtocol, p: dyn InboundProtocol)
Sets the SOCKS4 inbound of a mixed protocol.

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

MixedSetHttp

func MixedSetHttp(m &: MixedProtocol, p: dyn InboundProtocol)
Sets the HTTP proxy inbound of a mixed protocol.

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

ListenTcp

func ListenTcp(port: int): TcpInbound
Binds a TCP port (0 picks a free one) and returns the listener with its worker pool. Check Ok.

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

ListenTcpAt

func ListenTcpAt(host: str, port: int): TcpInbound
Like ListenTcp, for a given address such as ::1 or ::.

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

ListenTcpDualInbound

func ListenTcpDualInbound(port: int): TcpInbound
A TcpInbound listening on :: for both families.

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

ServeBackground

func ServeBackground(inb: TcpInbound, proto: dyn InboundProtocol, h: dyn Handler)
Starts Serve on its own thread and returns at once.

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

ServeForever

func ServeForever(inb: TcpInbound, proto: dyn InboundProtocol, h: dyn Handler)
Runs Serve on the calling thread. It returns only after Stop.

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

NewForwarder

func NewForwarder(dialer: dyn Dialer, proto: dyn InboundProtocol): ForwardHandler
Makes a ForwardHandler from a dialer and the inbound protocol that answers clients, with fresh counters.

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

NewGuardedForwarder

func NewGuardedForwarder(guard: GuardDialer, proto: dyn InboundProtocol): ForwardHandler
A forwarder that dials through a GuardDialer and tells the inbound when the policy refused the target.

NewChain

func NewChain(base: dyn Dialer): Chain
Starts an empty chain on top of a base dialer. With no hops it behaves like the base dialer.

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

ChainAdd

func ChainAdd(c &: Chain, hop: dyn Connector)
Appends a proxy hop to a chain.

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

NewFallback

func NewFallback(): Fallback
Returns an empty fallback dialer.

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

FallbackAdd

func FallbackAdd(f &: Fallback, d: dyn Dialer)
Appends a dialer to try after the earlier ones.

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

Direct

func Direct(): dyn Dialer
Returns a DirectDialer as a Dialer.

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

Block

func Block(): dyn Dialer
Returns a BlockDialer as a Dialer.

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

Via

func Via(upstream: dyn Dialer, hop: dyn Connector): dyn Dialer
Returns a dialer that goes through one hop, starting from the given upstream dialer. A shortcut for a ProxyDialer.

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

ParseIPv4

func ParseIPv4(s: str): Vector<int>
Parses dotted IPv4 text into four numbers. Returns an empty vector for anything that is not a strict IPv4 address.

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

ParseIPv6

func ParseIPv6(text: str): Vector<int>
Parses IPv6 text, including :: and an embedded IPv4 tail, into sixteen numbers. Returns an empty vector on error.

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

ParseIP

func ParseIP(s: str): Vector<int>
Parses IPv4 or IPv6 text. The result has 4 or 16 numbers, or none.

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

FormatIP

func FormatIP(b: Vector<int>): str
Turns 4 or 16 numbers back into text. IPv6 comes out in the shortest :: form.

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

IsIP

func IsIP(s: str): bool
True when the text is a valid IPv4 or IPv6 address.

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

NewAddr

func NewAddr(host: str, port: int): Addr
Builds an Addr from a host and a port and sets its kind by looking at the host. Brackets around an IPv6 host are accepted.

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

ParseAddr

func ParseAddr(s: str): Addr
Parses host:port, 1.2.3.4:80 or [::1]:80. A missing or out of range port makes valid false.

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

EncodeSocks

func EncodeSocks(a: Addr): Vector<u8>
Writes an address in the SOCKS5 wire form: a type byte, the host and a two byte port.
net-tunnel-Wire.salam
import net.tunnel
import mem

func hex(v: Vector<u8>): str:
    digits := "0123456789abcdef"
    mut out := ""
    repeat v.len() in i:
        b := v[i] as int
        out += digits.substr(b / 16, 1) + digits.substr(b % 16, 1)
    end
    ret out
end

func main:
    target := tunnel.NewAddr("example.com", 443)
    socks := tunnel.EncodeSocks(target)
    vless := tunnel.EncodeVless(target)
    println "socks5", hex(socks)
    println "vless ", hex(vless)
    p := tunnel.VectorBytes(socks)
    back := tunnel.DecodeSocks(p, socks.len())
    println back.valid, back.String(), back.size == socks.len()
    q := tunnel.VectorBytes(vless)
    again := tunnel.DecodeVless(q, vless.len())
    println again.valid, again.String()
    short := tunnel.DecodeSocks(p, 3)
    println "short", short.valid
    mem.Free(p)
    mem.Free(q)

    pair := tunnel.NewPipe()
    a := tunnel.PipeA(pair)
    b := tunnel.PipeB(pair)
    tunnel.WriteAllBytes(a, tunnel.VectorBytes(socks), socks.len())
    println tunnel.ReadSocksAddr(b).String()
    tunnel.WriteAllBytes(a, tunnel.VectorBytes(vless), vless.len())
    println tunnel.ReadVlessAddr(b).String()
end
Outputsocks5 030b6578616d706c652e636f6d01bb vless 01bb020b6578616d706c652e636f6d true example.com:443 true true example.com:443 short false example.com:443 example.com:443

EncodeVless

func EncodeVless(a: Addr): Vector<u8>
Writes an address in the VLESS wire form: the port first, then the type byte and the host.

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

DecodeSocks

func DecodeSocks(ptr: void*, n: int): Addr
Reads a SOCKS5 address from memory. valid is false when the bytes are short or wrong; size tells how many were used.

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

DecodeVless

func DecodeVless(ptr: void*, n: int): Addr
Reads a VLESS address from memory, the same way DecodeSocks does for SOCKS5.

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

VectorBytes

func VectorBytes(v: Vector<u8>): void*
Copies a vector of bytes into freshly allocated memory and returns the pointer. Free it with mem.Free.

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

ReadSocksAddr

func ReadSocksAddr(s: dyn Stream): Addr
Reads a SOCKS5 address from a stream, byte by byte, and returns it. Stops early when the stream ends.

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

ReadVlessAddr

func ReadVlessAddr(s: dyn Stream): Addr
Reads a VLESS address from a stream.

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

WatchHandshake

func WatchHandshake(fd: i64, total_ms: int): i64
Starts a total deadline for a descriptor. When it passes, the socket is shut down, however slowly the peer sends. Returns a ticket; 0 when there is nothing to watch.

WatchDone

func WatchDone(ticket: i64): bool
Ends a watch. False when it already fired or the ticket is unknown. Call it before closing the descriptor.

WatchExpired

func WatchExpired(): i64
How many watches have fired.

NewDomainSet

func NewDomainSet(): DomainSet
Makes an empty domain set.

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

NewIpSet

func NewIpSet(): IpSet
Makes an empty address set.

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

DomainSetFull

func DomainSetFull(s &: DomainSet, host: str)
Adds an exact name.

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

DomainSetSuffix

func DomainSetSuffix(s &: DomainSet, host: str)
Adds a suffix.

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

DomainSetKeyword

func DomainSetKeyword(s &: DomainSet, word: str)
Adds a keyword.

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

DomainSetRegex

func DomainSetRegex(s &: DomainSet, pattern: str): bool
Adds a regular expression. False when it is invalid.

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

DomainSetContains

func DomainSetContains(s: DomainSet, host: str): bool
True when the lower case host name is in the set.

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

IpSetAddNet

func IpSetAddNet(s &: IpSet, net: Vector<int>, prefix: int)
Adds a network given as numbers and a prefix length.

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

IpSetAdd

func IpSetAdd(s &: IpSet, cidr: str): bool
Adds a network or a single address from text such as 10.0.0.0/8. False when it cannot be read.

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

IpSetContains

func IpSetContains(s: IpSet, ip: Vector<int>): bool
True when the address, given as 4 or 16 numbers, is inside a network of the set.

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

ParseDomainList

func ParseDomainList(text: str, attr: str): DomainSet
Reads a domain list in the v2ray format: one entry per line, domain:, full:, keyword:, regexp:, include:name, comments with # and attributes such as @ads. A second argument keeps only entries with that attribute, or without it when it starts with !.

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

ParseIpList

func ParseIpList(text: str): IpSet
Reads a list of CIDR networks, one per line, with # comments.

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

SetGeoDir

func SetGeoDir(dir: str)
Sets the directory searched for lists named in geosite: and geoip: rules. It holds files such as ir.txt, geosite/ir.txt or geoip/ir.txt.

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

RegisterGeoSite

func RegisterGeoSite(name: str, set: DomainSet)
Registers a domain set under a name.

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

RegisterGeoIp

func RegisterGeoIp(name: str, set: IpSet)
Registers an address set under a name.

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

ClearGeo

func ClearGeo()
Forgets every registered list and the directory.

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

LookupGeoSite

func LookupGeoSite(spec: str): DomainSet
Finds a domain list by name, from the registry, then the directory, then the built in list (ir, category-ir). A name may end in @attribute. Unknown names give an empty set.

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

HasGeoSite

func HasGeoSite(spec: str): bool
True when the domain list exists and is not empty.

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

LookupGeoIp

func LookupGeoIp(name: str): IpSet
Finds an address list by name from the registry or the directory.

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

HasGeoIp

func HasGeoIp(name: str): bool
True when the address list exists and is not empty.

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

LoadGeoSiteDat

func LoadGeoSiteDat(ptr: void*, n: int, names: Vector<str>): int
Reads a geosite.dat from memory (the protobuf file of v2ray and its forks) and registers the lists. A vector of names limits what is loaded; empty means all. Returns how many lists were loaded. Truncated data stops cleanly.

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

LoadGeoIpDat

func LoadGeoIpDat(ptr: void*, n: int, names: Vector<str>): int
Reads a geoip.dat from memory and registers the lists. The reverse match flag is ignored.

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

LoadGeoSiteFile

func LoadGeoSiteFile(path: str, names: Vector<str>): int
Reads a geosite.dat file. Returns the number of lists, or -1 when the file cannot be read.

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

LoadGeoIpFile

func LoadGeoIpFile(path: str, names: Vector<str>): int
Reads a geoip.dat file. Returns the number of lists, or -1 when the file cannot be read.

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

NewStats

func NewStats(): Stats
Returns counters that start at zero.

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

FreeStats

func FreeStats(s: Stats)
Releases the counters.

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

NewIdleTimer

func NewIdleTimer(limit_ms: i64): IdleTimer
Returns a timer that has just seen activity.

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

RelayWith

func RelayWith(a: dyn Stream, b: dyn Stream, st: Stats, idle_ms: int): RelayResult
Copies data both ways between two streams until one side ends or the idle limit passes, updating the counters. Blocks, then closes both streams.

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

Relay

func Relay(a: dyn Stream, b: dyn Stream): RelayResult
RelayWith without counters or idle limit. Returns how many bytes moved each way.

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

PctDecode

func PctDecode(s: str): str
Decodes %XX escapes. A broken escape is kept as it is.

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

PctEncode

func PctEncode(s: str): str
Percent encodes text for a link: everything except letters, digits and -_.~.

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

B64Decode

func B64Decode(s: str): str
Decodes Base64, standard or URL safe, with or without padding. Line breaks are ignored.

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

B64EncodeUrl

func B64EncodeUrl(s: str): str
Encodes text as URL safe Base64 without padding.

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

func ParseLink(link: str): Node
Parses a vmess://, vless://, trojan:// or ss:// share link into a Node. Check valid.

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

func NodeToLink(n: Node): str
Writes a node as a share link. Parsing it again gives the same node.

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

ParseSubscription

func ParseSubscription(text: str): Subscription
Parses a subscription: Base64 or plain text with one link per line. Blank lines and # comments are skipped and bad lines are counted.

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

NodeAddr

func NodeAddr(n: Node): Addr
Returns the node's server as an Addr.

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

NodeLabel

func NodeLabel(n: Node): str
Returns a display name: the remark, or protocol://host:port when there is none.

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

RegisterOutbound

func RegisterOutbound(m: dyn OutboundMaker)
Adds an outbound maker to the registry. A maker for a protocol that is already registered replaces the old one.

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

RegisterInbound

func RegisterInbound(m: dyn InboundMaker)
Adds an inbound maker to the registry. A maker for a protocol that is already registered replaces the old one.

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

HasOutbound

func HasOutbound(name: str): bool
True when an outbound protocol name is registered. freedom and blackhole always are.

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

HasInbound

func HasInbound(name: str): bool
True when an inbound protocol name is registered.

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

CreateOutbound

func CreateOutbound(cfg: OutboundConfig): dyn Dialer
Builds the dialer for an outbound config. An unknown protocol gives a BlockDialer, so traffic is refused rather than leaked.

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

CreateInbound

func CreateInbound(cfg: InboundConfig): dyn InboundProtocol
Builds the inbound protocol for an inbound config. An unknown protocol gives a NullProtocol.

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

ParseConfig

func ParseConfig(text: str): Config
Parses v2ray style JSON into a Config. Problems are collected in errors instead of stopping.

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

LoadConfig

func LoadConfig(path: str): Config
Reads a config file and parses it. A file that cannot be read gives a config with one error.

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

ValidateConfig

func ValidateConfig(c: Config): Vector<str>
Checks a config for duplicate tags, bad ports, rules that point nowhere and the like. Returns one readable line per problem.

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

ConfigToJson

func ConfigToJson(c: Config): str
Writes a config back as JSON. Parsing the result gives the same config.

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

BuildRouter

func BuildRouter(c: Config): Router
Turns the rules of a config into a Router whose default is the config's default outbound.

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

OutboundFromNode

func OutboundFromNode(n: Node, tag: str): OutboundConfig
Converts a parsed share link (Node) into an OutboundConfig with the given tag.

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

NodeFromOutbound

func NodeFromOutbound(o: OutboundConfig): Node
The reverse of OutboundFromNode: reads an outbound config back into a Node.

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

ConfigFromNodes

func ConfigFromNodes(nodes: Vector<Node>, listen_port: int): Config
Builds a ready config from subscription nodes: one outbound per node, plus a mixed inbound on the given port, direct and block outbounds, and one rule that sends .ir domains and private addresses direct. The first node is the default.

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

NewConnLimits

func NewConnLimits(max_connections: int, max_per_source: int, deadline_ms: int): ConnLimits
Creates the limits of a server: connection cap, handshakes per source and the total handshake deadline.

LimitsEnter

func LimitsEnter(l: ConnLimits, fd: i64): Admit
Admits a new connection if the caps allow, and starts its deadline. Close the descriptor when ok is false.

LimitsHandshakeDone

func LimitsHandshakeDone(l: ConnLimits, a &: Admit)
Ends the handshake phase of an admitted connection.

LimitsRelease

func LimitsRelease(l: ConnLimits, a &: Admit)
Gives the slot back when the connection ends.

LimitsActive

func LimitsActive(l: ConnLimits): i64
How many connections are admitted now.

Go

func Go(fn: i64, arg: void*): bool
Runs a function on a new thread that cleans up after itself. The function takes one pointer and returns nothing. Handshake and connection threads should use this and not spawn, because a thread that is never joined keeps its stack (8 MB of address space) for good.

NewRule

func NewRule(tag: str): Rule
Returns an empty rule that points at a tag.

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

RuleDomain

func RuleDomain(r &: Rule, host: str)
Adds an exact host condition.

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

RuleSuffix

func RuleSuffix(r &: Rule, suffix: str)
Adds a domain suffix condition. ir matches ir and a.b.ir but not evilir.

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

RuleKeyword

func RuleKeyword(r &: Rule, word: str)
Adds a condition that the host contains a word.

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

RuleRegex

func RuleRegex(r &: Rule, pattern: str): bool
Adds a regular expression condition. False, and nothing added, when the pattern is not valid.

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

ParseCidr

func ParseCidr(text: str): Matcher
Parses 10.0.0.0/8 or a single address into a matcher. Its kind is 0 on error.

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

RuleCidr

func RuleCidr(r &: Rule, text: str): bool
Adds a CIDR condition. False when the text is not a valid range.

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

RulePrivate

func RulePrivate(r &: Rule)
Adds the private and local ranges: 10/8, 172.16/12, 192.168/16, loopback, link local, 100.64/10, their IPv6 counterparts and localhost.

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

RulePort

func RulePort(r &: Rule, spec: str): bool
Adds port conditions from text such as 80,443,8000-8100. False when any part is bad.

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

RuleDomainSpec

func RuleDomainSpec(r &: Rule, spec: str): bool
Adds a domain condition from v2ray style text: domain:, suffix:, full:, keyword:, regexp: or a bare word as a keyword.

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

RuleIpSpec

func RuleIpSpec(r &: Rule, spec: str): bool
Adds an IP condition from text: a CIDR, an address, or geoip:private.

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

RuleMatches

func RuleMatches(r: Rule, target: Addr): bool
True when a target satisfies a rule. A domain target is tested against domain matchers, an IP target against CIDR matchers.

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

RuleMatchesResolved

func RuleMatchesResolved(r: Rule, target: Addr, ips: Vector<Vector<int>>): bool
Like RuleMatches, but CIDR and geoip conditions also test the given resolved addresses of a domain target.

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

SetResolver

func SetResolver(rt &: Router, r: dyn HostResolver, ttl_ms: i64)
Gives a router a resolver and an answer lifetime in milliseconds. After that, a domain target can match CIDR rules.

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

RuleGeoSite

func RuleGeoSite(r &: Rule, name: str): bool
Adds a named domain list to a rule. False when the list is unknown.

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

RuleGeoIp

func RuleGeoIp(r &: Rule, name: str): bool
Adds a named address list to a rule. False when the list is unknown.

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

NewRouter

func NewRouter(default_tag: str): Router
Returns a router with no rules and the given default tag.

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

RouterAdd

func RouterAdd(rt &: Router, r: Rule)
Appends a rule. Rules are tried in the order they were added.

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

ParseRules

func ParseRules(text: str, default_tag: str): RuleSet
Parses a rule list, one rule per line: DOMAIN, DOMAIN-SUFFIX, DOMAIN-KEYWORD, DOMAIN-REGEX, IP-CIDR, IP-CIDR6, GEOIP,PRIVATE, DST-PORT and FINAL, each followed by a value and a tag. Comments start with #.

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

LoadRules

func LoadRules(path: str, default_tag: str): RuleSet
Reads a rule file and parses it with ParseRules.

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

IranRouter

func IranRouter(direct_tag: str, proxy_tag: str): Router
A ready router for Iran: .ir domains (also the Persian .ایران forms), private and local addresses and a few well known domestic services go to the direct tag, everything else to the proxy tag.

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