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
Streamis 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
Addris a target: a host name or IP and a port. Every dialer and every inbound speaks inAddrvalues. - A
Dialeropens an outgoing connection.DirectDialer,BlockDialer,Chain(several proxy hops in a row),FallbackandRouterDialerare all dialers, and they nest. AConnectoris one proxy hop that a chain can use. - An
InboundProtocolreads a client's request and answers it;ListenTcpaccepts connections and aHandler(usuallyForwardHandler) serves them.MixedProtocolputs SOCKS4, SOCKS5 and HTTP proxy on one port. - A
Routerturns a target into a tag (direct,proxy,block) using ordered rules, withIranRouteras a ready preset. ParseLinkandParseSubscriptionreadvmess://,vless://,trojan://andss://share links, andParseConfigreads v2ray style JSON.
Things that catch people out:
- Failure is a closed stream, not an exception. A dial that fails returns a stream whose
Okis false. Check it before you write. - A config does not build protocols by itself.
BuildDialerknowsfreedom(direct) andblackhole(block). Any other outbound protocol needs a maker registered withRegisterOutbound; 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
::1and::(both families),DirectDialerreaches 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.
ParseLinkrejects what it cannot read, but a subscription comes from someone else's server. Look atallow_insecurebefore you trust a node. - A
Statsvalue and aIdleTimerown native counters. Free them withFreeStatsandFreewhen 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.
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)
endThe 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.
IranRoutersends.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 withRuleSuffixor a rule file forParseRules. - 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.
- Read the link or the subscription.
ParseLinkreads onevless://,vmess://,trojan://orss://link andParseSubscriptionreads a text with many links (Base64 or one link per line). A bad line is dropped and counted inskipped; the program does not stop. A link that comes from someone else is untrusted input: look atallow_insecurebefore you trust a node. - Build a config.
ConfigFromNodesmakes a mixed inbound (SOCKS4, SOCKS5 and HTTP on one local port), one outbound per node, thedirectandblockoutbounds and the.irrule. With no extra work,.irdomains and private addresses go direct and everything else goes through your node. - Turn on DoH and fragmenting. With
RegisterWith, thefragmentoption sends the ClientHello in several packets anduse_dohresolves names with DNS over HTTPS. Both are only options: they help on some networks and not on others. - Start.
StartAllbrings the inbounds up andStopcloses each one. If the config is a V2Ray style JSON file,RunConfigloads it, checks it and runs it until Ctrl-C. - Connect your programs. The inbound listens on
127.0.0.1only. 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.comresolves the name through the proxy too.
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])
endThe 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
Statsand anIdleTimermust be freed. ATcpInboundstops withStop; connections already relayed carry on to their end. TcpInboundserves 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 Pendingimport 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()
endSourceTable
struct SourceTable| Name | Type | Description |
|---|---|---|
core | void* | The shared state. |
The example for this item is shown with the ones above: net-tunnel-Inbound.salam
Acceptor
interface AcceptorThe example for this item is shown with the ones above: net-tunnel-Inbound.salam
ReadyConn
struct ReadyConn| Name | Type | Description |
|---|---|---|
stream | dyn Stream | The stream. |
ok | bool = false | True when the accept worked. |
remote | str = "" | The address of the client. |
The example for this item is shown with the ones above: net-tunnel-Inbound.salam
Valid
func Valid(): boolThe example for this item is shown with the ones above: net-tunnel-Inbound.salam
Open
func Open(): dyn StreamThe example for this item is shown with the ones above: net-tunnel-Inbound.salam
Abort
func Abort()The example for this item is shown with the ones above: net-tunnel-Inbound.salam
Remote
func Remote(): strThe example for this item is shown with the ones above: net-tunnel-Inbound.salam
TcpAcceptor
struct TcpAcceptor| Name | Type | Description |
|---|---|---|
server | tcp.Server | The listening socket. |
The example for this item is shown with the ones above: net-tunnel-Inbound.salam
Accept
func Accept(): dyn PendingThe example for this item is shown with the ones above: net-tunnel-Inbound.salam
Port
func Port(): intThe example for this item is shown with the ones above: net-tunnel-Inbound.salam
Ok
func Ok(): boolThe example for this item is shown with the ones above: net-tunnel-Inbound.salam
Close
func Close()The example for this item is shown with the ones above: net-tunnel-Inbound.salam
AcceptServer
struct AcceptServer| Name | Type | Description |
|---|---|---|
acceptor | dyn Acceptor | The acceptor. |
host | str = "127.0.0.1" | The address it listens on. |
running | atomic.Flag | Cleared by Stop. |
workers | pool.Pool | The pool that serves connections. |
handshake_ms | int = 8000 | How long a client has to finish the handshake. The default is 8000. |
per_source | int = 0 | How many connections of one address may be in their handshake at once. 0 means no limit. |
sources | SourceTable | The handshakes in progress per address. |
The example for this item is shown with the ones above: net-tunnel-Inbound.salam
Ok
func Ok(): boolThe example for this item is shown with the ones above: net-tunnel-Inbound.salam
Port
func Port(): intThe example for this item is shown with the ones above: net-tunnel-Inbound.salam
Stop
func Stop()The example for this item is shown with the ones above: net-tunnel-Inbound.salam
DestPolicy
struct DestPolicy| Name | Type | Description |
|---|---|---|
allow_private | bool = false | Allows private and loopback targets, but only when loopback_only is true (set by PolicyForBind). |
loopback_only | bool = false | True when the listener is bound to loopback only. |
allow_cidrs | Vector | Ranges that stay reachable even if special. |
deny_cidrs | Vector | Ranges that are always refused; they win over every allow entry. |
allow_suffixes | Vector | Domain suffixes that stay reachable. |
deny_suffixes | Vector | Domain suffixes that are always refused. |
resolver | dyn HostResolver | The resolver used to look names up before the check. The system resolver when none is set. |
has_resolver | bool = false | True when resolver is set. |
Plan
struct Plan| Name | Type | Description |
|---|---|---|
allowed | bool = false | True when the target may be reached. |
reason | str = "" | Why a target was refused. |
ips | Vector | The checked addresses; dial these, never the name again. |
resolved | bool = false | True when the name was resolved. |
GuardResult
struct GuardResult| Name | Type | Description |
|---|---|---|
stream | dyn Stream | The connected stream, or a closed one. |
denied | bool = false | True when the policy refused the target. |
reason | str = "" | Why it was refused. |
GuardDialer
struct GuardDialer| Name | Type | Description |
|---|---|---|
inner | dyn Dialer | The dialer used for the connection. |
policy | DestPolicy | The destination policy. |
router | RouterDialer | A router dialer used instead of inner. |
has_router | bool = false | True when router is in use. |
DialChecked
func DialChecked(target: Addr): GuardResultDial
func Dial(target: Addr): dyn StreamName
func Name(): strPipeStream
struct PipeStreamNewPipe. What one end writes the other reads. Good for tests and for joining two pieces of code without a socket.| Name | Type | Description |
|---|---|---|
rx | _Ring | The buffer this end reads from. |
tx | _Ring | The buffer this end writes to. |
deadline | int = 0 | The timeout in milliseconds. |
live | bool = false | False once the stream has been closed. |
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()
endRead
func Read(max: int): strRead.The example for this item is shown with the ones above: net-tunnel-Stream.salam
ReadBytes
func ReadBytes(buf: void*, max: int): intReadBytes.The example for this item is shown with the ones above: net-tunnel-Stream.salam
Write
func Write(data: str): intWrite.The example for this item is shown with the ones above: net-tunnel-Stream.salam
WriteBytes
func WriteBytes(ptr: void*, n: int): intWriteBytes.The example for this item is shown with the ones above: net-tunnel-Stream.salam
SetDeadline
func SetDeadline(millis: int)The example for this item is shown with the ones above: net-tunnel-Stream.salam
Shutdown
func Shutdown()Shutdown.The example for this item is shown with the ones above: net-tunnel-Stream.salam
CloseWrite
func CloseWrite()The example for this item is shown with the ones above: net-tunnel-Stream.salam
Close
func Close()The example for this item is shown with the ones above: net-tunnel-Stream.salam
Ok
func Ok(): boolThe example for this item is shown with the ones above: net-tunnel-Stream.salam
PipePair
struct PipePairPipeA and PipeB.| Name | Type | Description |
|---|---|---|
a | PipeStream | The first end. |
b | PipeStream | The second end. |
The example for this item is shown with the ones above: net-tunnel-Stream.salam
PrefixStream
struct PrefixStreamMixedProtocol uses it to put back the byte it peeked at.| Name | Type | Description |
|---|---|---|
inner | dyn Stream | The wrapped stream. |
prefix | str = "" | The bytes to give first. |
off | int = 0 | How 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): strRead.The example for this item is shown with the ones above: net-tunnel-Stream.salam
ReadBytes
func ReadBytes(buf: void*, max: int): intReadBytes.The example for this item is shown with the ones above: net-tunnel-Stream.salam
Write
func Write(data: str): intWrite.The example for this item is shown with the ones above: net-tunnel-Stream.salam
WriteBytes
func WriteBytes(ptr: void*, n: int): intWriteBytes.The example for this item is shown with the ones above: net-tunnel-Stream.salam
SetDeadline
func SetDeadline(millis: int)The example for this item is shown with the ones above: net-tunnel-Stream.salam
Shutdown
func Shutdown()Shutdown.The example for this item is shown with the ones above: net-tunnel-Stream.salam
CloseWrite
func CloseWrite()The example for this item is shown with the ones above: net-tunnel-Stream.salam
Close
func Close()The example for this item is shown with the ones above: net-tunnel-Stream.salam
Ok
func Ok(): boolThe example for this item is shown with the ones above: net-tunnel-Stream.salam
RouterDialer
struct RouterDialerForwardHandler.| Name | Type | Description |
|---|---|---|
router | Router | The router that picks tags. |
tags | Vector | The registered tags, in order. |
dialers | Vector | The dialers, one for each tag. |
last_tag | str = "" | The tag chosen for the most recent dial, handy in logs and tests. |
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()
endPick
func Pick(tag: str): dyn DialerThe example for this item is shown with the ones above: net-tunnel-RouterDialer.salam
Dial
func Dial(target: Addr): dyn StreamThe example for this item is shown with the ones above: net-tunnel-RouterDialer.salam
Name
func Name(): strrouter.The example for this item is shown with the ones above: net-tunnel-RouterDialer.salam
Session
struct Session| Name | Type | Description |
|---|---|---|
ok | bool = false | True when the handshake succeeded and the target is known. |
target | Addr | The address the client wants to reach. |
protocol | str = "" | Which protocol the client spoke, such as socks5 or http. |
user | str = "" | The authenticated user name, empty when there was none. |
command | int = 1 | CMD_CONNECT or CMD_UDP. |
initial | str = "" | 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_data | void* | 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_size | int = 0 | The number of bytes in initial_data; zero when the session uses initial or has none. |
error | str = "" | Why the handshake failed, for logs. |
wrapped | bool = false | True 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. |
stream | dyn Stream | The replacement stream, valid only when wrapped is true. |
refused | bool = false | True 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 InboundProtocolThe example for this item is shown with the ones above: net-tunnel-Inbound.salam
Handler
interface HandlerSession 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| Name | Type | Description |
|---|---|---|
unused | int = 0 | Not used. |
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
endName
func Name(): strnull.The example for this item is shown with the ones above: net-tunnel-Mixed.salam
Handshake
func Handshake(_s: dyn Stream): SessionThe example for this item is shown with the ones above: net-tunnel-Mixed.salam
Reply
func Reply(_s: dyn Stream, _sess: Session, _ok: bool)The example for this item is shown with the ones above: net-tunnel-Mixed.salam
MixedProtocol
struct MixedProtocolNewMixed.| Name | Type | Description |
|---|---|---|
socks4 | dyn InboundProtocol | The SOCKS4 inbound, when set. |
socks5 | dyn InboundProtocol | The SOCKS5 inbound, when set. |
http | dyn InboundProtocol | The HTTP proxy inbound, when set. |
has_socks4 | bool = false | True after MixedSetSocks4. |
has_socks5 | bool = false | True after MixedSetSocks5. |
has_http | bool = false | True after MixedSetHttp. |
The example for this item is shown with the ones above: net-tunnel-Mixed.salam
Name
func Name(): strmixed.The example for this item is shown with the ones above: net-tunnel-Mixed.salam
Handshake
func Handshake(s: dyn Stream): SessionDetectMixed, 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)The example for this item is shown with the ones above: net-tunnel-Mixed.salam
ForwardHandler
struct ForwardHandlerHandler: dials the target with its dialer, answers the client, forwards any initial bytes and then relays both directions until the connection ends.| Name | Type | Description |
|---|---|---|
dialer | dyn Dialer | The dialer that reaches the target. Use a RouterDialer here to route by rules. |
proto | dyn InboundProtocol | The inbound protocol, used to answer the client. |
stats | Stats | Counters for connections and bytes. |
idle_ms | int = 300000 | Close a relayed connection after this many milliseconds without traffic. The default is 300000; 0 means never. |
guard | GuardDialer | The guarded dialer used when has_guard is true. |
has_guard | bool = false | True 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)The example for this item is shown with the ones above: net-tunnel-Inbound.salam
TcpInbound
struct TcpInboundServeBackground or ServeForever.| Name | Type | Description |
|---|---|---|
port | int = 0 | The port it listens on. When you asked for port 0 this is the free port the system picked. |
host | str = "" | The address it listens on; used to wake the accept loop when stopping. |
listener | tcp.Server | The underlying TCP listener. |
running | atomic.Flag | A flag that Stop clears. |
workers | pool.Pool | The pool that serves connections. Each client gets a task. |
handshake_ms | int = 8000 | How long a client has to finish its handshake, in milliseconds. The default is 8000. |
per_source | int = 0 | How many connections of one address may be in their handshake at once. 0 means no limit. |
sources | SourceTable | The handshakes in progress per address. |
The example for this item is shown with the ones above: net-tunnel-Inbound.salam
Ok
func Ok(): boolThe example for this item is shown with the ones above: net-tunnel-Inbound.salam
Serve
func Serve(proto: dyn InboundProtocol, h: dyn Handler)Stop.The example for this item is shown with the ones above: net-tunnel-Inbound.salam
Stop
func Stop()The example for this item is shown with the ones above: net-tunnel-Inbound.salam
Dialer
interface DialerStream. Direct, blocking, proxied and routed dialers all implement it.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()
endConnector
interface Connectorimport 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()
endDirectDialer
struct DirectDialer| Name | Type | Description |
|---|---|---|
timeout_ms | int = 10000 | How long a connect may take, in milliseconds. The default is 10000. |
resolver | dyn HostResolver | Optional resolver for domain targets, for example one backed by DNS over HTTPS. |
has_resolver | bool = false | True to use the resolver. Each address is tried in turn within the time limit. |
prefer_v6 | bool = false | Try 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 StreamThe example for this item is shown with the ones above: net-tunnel-Dialers.salam
Name
func Name(): strdirect.The example for this item is shown with the ones above: net-tunnel-Dialers.salam
BlockDialer
struct BlockDialerblackhole outbound becomes.| Name | Type | Description |
|---|---|---|
refused | int = 0 | How 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 StreamThe example for this item is shown with the ones above: net-tunnel-Dialers.salam
Name
func Name(): strblock.The example for this item is shown with the ones above: net-tunnel-Dialers.salam
ProxyDialer
struct ProxyDialer| Name | Type | Description |
|---|---|---|
upstream | dyn Dialer | The dialer used to reach the hop's server. |
hop | dyn Connector | The 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 StreamThe example for this item is shown with the ones above: net-tunnel-Chain.salam
Name
func Name(): strThe example for this item is shown with the ones above: net-tunnel-Chain.salam
NullConnector
struct NullConnector| Name | Type | Description |
|---|---|---|
unused | int = 0 | Not used. A struct needs at least one field. |
Server
func Server(): AddrHandshake
func Handshake(_s: dyn Stream, _target: Addr): boolName
func Name(): strnull.Chain
struct ChainNewChain and ChainAdd.| Name | Type | Description |
|---|---|---|
base | dyn Dialer | The dialer that reaches the first hop. |
hops | Vector | The 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 StreamThe example for this item is shown with the ones above: net-tunnel-Chain.salam
Name
func Name(): strchain>http>socks5.The example for this item is shown with the ones above: net-tunnel-Chain.salam
Fallback
struct Fallback| Name | Type | Description |
|---|---|---|
items | Vector | The 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 StreamThe example for this item is shown with the ones above: net-tunnel-Dialers.salam
Name
func Name(): strThe example for this item is shown with the ones above: net-tunnel-Dialers.salam
Addr
struct AddrParseAddr and NewAddr fill it in.| Name | Type | Description |
|---|---|---|
host | str = "" | The host name or IP text, without brackets for IPv6. |
port | int = 0 | The port, 0 to 65535. |
kind | int = 3 | ADDR_IPV4, ADDR_DOMAIN or ADDR_IPV6. The numbers are the ones the SOCKS5 address type uses. |
valid | bool = false | False when parsing failed. Always check it before using the address. |
size | int = 0 | Filled in by the decoders: how many bytes the encoded address took on the wire. |
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()
endString
func String(): strhost: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| Name | Type | Description |
|---|---|---|
full | HashMap | Exact names. |
suffix | HashMap | Suffixes; a name matches itself and every sub domain. |
keywords | Vector | Words that may appear anywhere in a name. |
regexes | Vector | Regular expressions. |
count | int = 0 | How many entries were added. |
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"))
endIpSet
struct IpSet| Name | Type | Description |
|---|---|---|
v4 | HashMap | IPv4 networks by prefix length and address. |
v4_lens | i64 = 0 | A bit mask of the IPv4 prefix lengths present. |
v6 | HashMap | IPv6 networks. |
v6_lens | Vector | The IPv6 prefix lengths present. |
count | int = 0 | How many networks were added. |
The example for this item is shown with the ones above: net-tunnel-Router.salam
Stats
struct StatsNewStats.| Name | Type | Description |
|---|---|---|
up | atomic.I64 | Bytes sent from the client side towards the target. |
down | atomic.I64 | Bytes sent from the target back to the client. |
total | atomic.I64 | Connections handled in total. |
active | atomic.I64 | Connections being relayed right now. |
failed | atomic.I64 | Connections whose target could not be reached. |
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)
endUp
func Up(): i64The example for this item is shown with the ones above: net-tunnel-Relay.salam
Down
func Down(): i64The example for this item is shown with the ones above: net-tunnel-Relay.salam
Total
func Total(): i64The example for this item is shown with the ones above: net-tunnel-Relay.salam
Active
func Active(): i64The example for this item is shown with the ones above: net-tunnel-Relay.salam
Failed
func Failed(): i64The example for this item is shown with the ones above: net-tunnel-Relay.salam
Summary
func Summary(): strup=... down=... total=... active=... failed=....The example for this item is shown with the ones above: net-tunnel-Relay.salam
RelayResult
struct RelayResultRelay moved in each direction.| Name | Type | Description |
|---|---|---|
up | i64 = 0 | Bytes from the first stream to the second. |
down | i64 = 0 | Bytes 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| Name | Type | Description |
|---|---|---|
last | atomic.I64 | The time of the last activity, in milliseconds. |
limit | i64 = 0 | How long it may be quiet, in milliseconds. 0 means no limit. |
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()
endTouch
func Touch()The example for this item is shown with the ones above: net-tunnel-Idle.salam
IdleMs
func IdleMs(): i64The example for this item is shown with the ones above: net-tunnel-Idle.salam
Expired
func Expired(): boolThe example for this item is shown with the ones above: net-tunnel-Idle.salam
Free
func Free()The example for this item is shown with the ones above: net-tunnel-Idle.salam
Node
struct NodeParseLink fills it and NodeToLink writes it back.| Name | Type | Description |
|---|---|---|
valid | bool = false | False when parsing failed; see error. |
error | str = "" | Why parsing failed. |
protocol | str = "" | vmess, vless, trojan or ss. |
remark | str = "" | The human name after the # in the link. |
host | str = "" | The server host name or IP. |
port | int = 0 | The server port. |
uuid | str = "" | The user id of VMess and VLESS. |
password | str = "" | The password of Trojan and Shadowsocks. |
method | str = "" | The cipher: the Shadowsocks method or the VMess security. |
alter_id | int = 0 | The VMess alter id, normally 0. |
flow | str = "" | The VLESS flow, such as xtls-rprx-vision. |
encryption | str = "" | The VLESS encryption field, normally none. |
network | str = "tcp" | The transport: tcp, ws, grpc and so on. The default is tcp. |
header_type | str = "" | The header obfuscation type of the transport. |
tls | bool = false | True when the link asks for TLS. |
security | str = "" | none, tls or reality. |
sni | str = "" | The server name sent in the TLS handshake. |
alpn | str = "" | The ALPN list, comma separated. |
fingerprint | str = "" | The client fingerprint name, such as chrome. |
path | str = "" | The WebSocket or HTTP path. |
host_header | str = "" | The Host header for WebSocket and HTTP transports. |
service_name | str = "" | The gRPC service name. |
grpc_mode | str = "" | multi for a gRPC link with mode=multi (or a vmess link with type multi), empty for gun. |
public_key | str = "" | The REALITY public key. |
short_id | str = "" | The REALITY short id. |
spider_x | str = "" | The REALITY spider path. |
plugin | str = "" | The Shadowsocks plugin string, if any. |
allow_insecure | bool = false | True when the link says to skip certificate checks. Treat this as a warning sign. |
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")
endSubscription
struct Subscription| Name | Type | Description |
|---|---|---|
nodes | Vector | The valid nodes in order. |
skipped | int = 0 | How 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 StreamThe example for this item is shown with the ones above: net-tunnel-Stream.salam
TcpStream
struct TcpStreamStream over a plain TCP connection. WrapTcp makes one.| Name | Type | Description |
|---|---|---|
conn | tcp.Conn | The TCP connection. |
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)
endRead
func Read(max: int): strRead.The example for this item is shown with the ones above: net-tunnel-Wrap.salam
ReadBytes
func ReadBytes(buf: void*, max: int): intReadBytes.The example for this item is shown with the ones above: net-tunnel-Wrap.salam
Write
func Write(data: str): intWrite.The example for this item is shown with the ones above: net-tunnel-Wrap.salam
WriteBytes
func WriteBytes(ptr: void*, n: int): intWriteBytes.The example for this item is shown with the ones above: net-tunnel-Wrap.salam
SetDeadline
func SetDeadline(millis: int)The example for this item is shown with the ones above: net-tunnel-Wrap.salam
Shutdown
func Shutdown()Shutdown.The example for this item is shown with the ones above: net-tunnel-Wrap.salam
CloseWrite
func CloseWrite()The example for this item is shown with the ones above: net-tunnel-Stream.salam
Close
func Close()The example for this item is shown with the ones above: net-tunnel-Wrap.salam
Ok
func Ok(): boolThe example for this item is shown with the ones above: net-tunnel-Wrap.salam
TlsStream
struct TlsStreamStream over a TLS connection. WrapTls makes one.| Name | Type | Description |
|---|---|---|
conn | tls.Conn | The TLS connection. |
Read
func Read(max: int): strRead.ReadBytes
func ReadBytes(buf: void*, max: int): intReadBytes.Write
func Write(data: str): intWrite.WriteBytes
func WriteBytes(ptr: void*, n: int): intWriteBytes.SetDeadline
func SetDeadline(millis: int)Shutdown
func Shutdown()Shutdown.CloseWrite
func CloseWrite()The example for this item is shown with the ones above: net-tunnel-Stream.salam
Close
func Close()Ok
func Ok(): boolWsStream
struct WsStreamStream over a WebSocket connection. WrapWebSocket makes one.| Name | Type | Description |
|---|---|---|
conn | websocket.Conn | The WebSocket connection. |
pending | void* | A message that was only partly read. |
pending_len | int = 0 | The size of the partly read message. |
pending_off | int = 0 | How much of it has been read. |
timeout | int = -1 | The 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): strRead.The example for this item is shown with the ones above: net-tunnel-Wrap.salam
ReadBytes
func ReadBytes(buf: void*, max: int): intReadBytes.The example for this item is shown with the ones above: net-tunnel-Wrap.salam
Write
func Write(data: str): intWrite.The example for this item is shown with the ones above: net-tunnel-Wrap.salam
WriteBytes
func WriteBytes(ptr: void*, n: int): intWriteBytes.The example for this item is shown with the ones above: net-tunnel-Wrap.salam
SetDeadline
func SetDeadline(millis: int)The example for this item is shown with the ones above: net-tunnel-Wrap.salam
Shutdown
func Shutdown()Shutdown.The example for this item is shown with the ones above: net-tunnel-Wrap.salam
CloseWrite
func CloseWrite()The example for this item is shown with the ones above: net-tunnel-Stream.salam
Close
func Close()The example for this item is shown with the ones above: net-tunnel-Wrap.salam
Ok
func Ok(): boolThe example for this item is shown with the ones above: net-tunnel-Wrap.salam
ClosedStream
struct ClosedStream| Name | Type | Description |
|---|---|---|
unused | int = 0 | Not used. |
The example for this item is shown with the ones above: net-tunnel-Stream.salam
Read
func Read(_max: int): strRead.The example for this item is shown with the ones above: net-tunnel-Stream.salam
ReadBytes
func ReadBytes(_buf: void*, _max: int): intReadBytes.The example for this item is shown with the ones above: net-tunnel-Stream.salam
Write
func Write(_data: str): intWrite.The example for this item is shown with the ones above: net-tunnel-Stream.salam
WriteBytes
func WriteBytes(_ptr: void*, _n: int): intWriteBytes.The example for this item is shown with the ones above: net-tunnel-Stream.salam
SetDeadline
func SetDeadline(millis: int)The example for this item is shown with the ones above: net-tunnel-Stream.salam
Shutdown
func Shutdown()Shutdown.The example for this item is shown with the ones above: net-tunnel-Stream.salam
CloseWrite
func CloseWrite()The example for this item is shown with the ones above: net-tunnel-Stream.salam
Close
func Close()The example for this item is shown with the ones above: net-tunnel-Stream.salam
Ok
func Ok(): boolThe example for this item is shown with the ones above: net-tunnel-Stream.salam
InboundConfig
struct InboundConfig| Name | Type | Description |
|---|---|---|
tag | str = "" | The name other parts of the config use for this inbound. |
protocol | str = "" | The protocol name, such as mixed, socks or http. It picks the registered InboundMaker. |
listen | str = "127.0.0.1" | The address to listen on. The default is 127.0.0.1. |
port | int = 0 | The port to listen on. A text port in the JSON is accepted too. |
settings | str = "" | The raw JSON of the settings object, kept as text for the protocol to read. |
stream_settings | str = "" | The raw JSON of streamSettings for an inbound, kept as text for the protocol to read. |
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()
endOutboundConfig
struct OutboundConfig| Name | Type | Description |
|---|---|---|
tag | str = "" | The name rules use to send traffic here. |
protocol | str = "" | The protocol name. freedom and blackhole are built in; others need a registered OutboundMaker. |
settings | str = "" | The raw JSON of the settings object. |
stream_settings | str = "" | 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| Name | Type | Description |
|---|---|---|
domain | Vector | Domain conditions such as domain:ir, full:a.com, keyword:bank or regexp:.... |
ip | Vector | IP conditions: CIDR ranges, single addresses or geoip:private. |
port | str = "" | A port condition such as 25,465 or 8000-8100. |
outbound_tag | str = "" | 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 ConfigParseConfig and LoadConfig produce it.| Name | Type | Description |
|---|---|---|
log_level | str = "warning" | The log level from the config. The default is warning. |
inbounds | Vector | The inbounds in file order. |
outbounds | Vector | The outbounds in file order. |
rules | Vector | The routing rules in file order. The first match wins. |
default_outbound | str = "" | The tag used when no rule matches. Without one, the first outbound is used. |
errors | Vector | Problems 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 OutboundMakerDialer for one outbound protocol. Register it with RegisterOutbound so configs can use the protocol name.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
endInboundMaker
interface InboundMakerInboundProtocol 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| Name | Type | Description |
|---|---|---|
active | i64* | Shared count of admitted connections. |
max_connections | int = 1024 | The cap on connections; 0 for none. |
max_per_source | int = 32 | Handshakes in progress allowed per source; 0 for none. |
deadline_ms | int = 10000 | Total handshake time in milliseconds; 0 for none. |
sources | SourceTable | Per source counts. |
Admit
struct Admit| Name | Type | Description |
|---|---|---|
ok | bool = false | True when the connection was admitted. |
host | str = "" | The source address. |
ticket | i64 = 0 | The deadline ticket. |
counted | bool = false | True when counted against the per source limit. |
held | bool = false | True while a slot is held. |
done | bool = false | True once the handshake phase ended. |
LookupResult
struct LookupResult| Name | Type | Description |
|---|---|---|
ips | Vector | IPv4 and IPv6 address texts. |
The example for this item is shown with the ones above: net-tunnel-Router.salam
HostResolver
interface HostResolverThe example for this item is shown with the ones above: net-tunnel-Router.salam
NullResolver
struct NullResolver| Name | Type | Description |
|---|---|---|
unused | int = 0 | Not used. |
The example for this item is shown with the ones above: net-tunnel-Router.salam
Lookup
func Lookup(_host: str): LookupResultThe example for this item is shown with the ones above: net-tunnel-Router.salam
ResolveCache
struct ResolveCache| Name | Type | Description |
|---|---|---|
mu | sync.Mutex | Lock for the cache. |
ips | HashMap | Addresses by host name. |
expires | HashMap | When each answer stops being valid, in milliseconds. |
ttl_ms | i64 = 300000 | How long an answer is kept. The default is 300000. |
max | int = 4096 | How many names to keep before the cache is emptied. |
lookups | int = 0 | How many real lookups were made. |
The example for this item is shown with the ones above: net-tunnel-Router.salam
Matcher
struct MatcherRule... functions rather than by hand.| Name | Type | Description |
|---|---|---|
kind | int = 0 | One of the M_ constants. |
value | str = "" | The text of the condition. |
net | Vector | For a CIDR: the network address as numbers. |
prefix | int = 0 | For a CIDR: the prefix length in bits. |
lo | int = 0 | For a port range: the lowest port. |
hi | int = 0 | For a port range: the highest port. |
dset | DomainSet | For a geosite matcher: the domain set. |
iset | IpSet | For a geoip matcher: the address set. |
The example for this item is shown with the ones above: net-tunnel-Router.salam
Rule
struct Rule| Name | Type | Description |
|---|---|---|
tag | str = "" | The outbound tag the rule picks. |
dest | Vector | Destination matchers: any one is enough. |
ports | Vector | Port matchers: any one is enough. |
The example for this item is shown with the ones above: net-tunnel-Router.salam
Router
struct Router| Name | Type | Description |
|---|---|---|
rules | Vector | The rules in the order they are tried. |
default_tag | str = "direct" | The tag returned when no rule matches. |
resolver | dyn HostResolver | Used to turn a domain target into addresses so that CIDR and geoip rules can match it. |
has_resolver | bool = false | True after SetResolver. |
cache | ResolveCache | The 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>>The example for this item is shown with the ones above: net-tunnel-Router.salam
Match
func Match(target: Addr): strThe example for this item is shown with the ones above: net-tunnel-Router.salam
MatchHost
func MatchHost(host: str, port: int): strThe example for this item is shown with the ones above: net-tunnel-Router.salam
RuleSet
struct RuleSetParseRules returns: the router and the list of lines it could not use.| Name | Type | Description |
|---|---|---|
router | Router | The router built from the good lines. |
errors | Vector | One message per bad line, with its line number. |
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)
endConstants and variables
Commands CMD_…
command.| Name | Value |
|---|---|
CMD_CONNECT | 1 |
CMD_UDP | 3 |
Mixed protocol kinds MIXED_…
DetectMixed returns.| Name | Value |
|---|---|
MIXED_UNKNOWN | 0 |
MIXED_HTTP | 1 |
MIXED_SOCKS4 | 4 |
MIXED_SOCKS5 | 5 |
Address kinds ADDR_…
kind.| Name | Value |
|---|---|
ADDR_IPV4 | 1 |
ADDR_DOMAIN | 3 |
ADDR_IPV6 | 4 |
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 example for this item is shown with the ones above: net-tunnel-Router.salam
DeadlineDirect
const DeadlineDirect: i32 = -2SetDeadline 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_…
kind.| Name | Value |
|---|---|
M_DOMAIN | 1 |
M_SUFFIX | 2 |
M_KEYWORD | 3 |
M_REGEX | 4 |
M_CIDR | 5 |
M_PORT | 6 |
M_GEOSITE | 7 |
M_GEOIP | 8 |
Functions
NewSourceTable
func NewSourceTable(): SourceTableThe example for this item is shown with the ones above: net-tunnel-Inbound.salam
SourceKey
func SourceKey(remote: str): strimport 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()
endSourceEnter
func SourceEnter(t: SourceTable, remote: str, limit: int): boolThe example for this item is shown with the ones above: net-tunnel-Inbound.salam
SourceLeave
func SourceLeave(t: SourceTable, remote: str)The example for this item is shown with the ones above: net-tunnel-Inbound.salam
NewAcceptServer
func NewAcceptServer(a: dyn Acceptor, host: str): AcceptServerThe example for this item is shown with the ones above: net-tunnel-Inbound.salam
ListenTcpOn
func ListenTcpOn(host: str, port: int): AcceptServer::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::, 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::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): AcceptServerThe 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)The example for this item is shown with the ones above: net-tunnel-Inbound.salam
NewDestPolicy
func NewDestPolicy(): DestPolicyPolicyAllowPrivate
func PolicyAllowPrivate(p &: DestPolicy, enabled: bool)allow_private on or off.PolicyAllowCidr
func PolicyAllowCidr(p &: DestPolicy, text: str): boolPolicyDenyCidr
func PolicyDenyCidr(p &: DestPolicy, text: str): boolPolicyAllowSuffix
func PolicyAllowSuffix(p &: DestPolicy, suffix: str)PolicyDenySuffix
func PolicyDenySuffix(p &: DestPolicy, suffix: str)PolicySetResolver
func PolicySetResolver(p &: DestPolicy, r: dyn HostResolver)IsLoopbackHost
func IsLoopbackHost(host: str): boolPolicyForBind
func PolicyForBind(p: DestPolicy, bind_host: str): DestPolicyallow_private only counts when the host is loopback.IsSpecialIP
func IsSpecialIP(b: Vector<int>): boolIsPrivateAddress
func IsPrivateAddress(ip: str): boolPolicyCheckBytes
func PolicyCheckBytes(p: DestPolicy, ip: Vector<int>): boolPolicyCheckIP
func PolicyCheckIP(p: DestPolicy, ip: str): boolPolicyCheckName
func PolicyCheckName(p: DestPolicy, host: str): boolPolicyPlan
func PolicyPlan(p: DestPolicy, a: Addr, resolve: bool): Planips from the result.Guard
func Guard(inner: dyn Dialer, p: DestPolicy): GuardDialerGuardRouter
func GuardRouter(rd: RouterDialer, p: DestPolicy): GuardDialerNewPipe
func NewPipe(): PipePairThe example for this item is shown with the ones above: net-tunnel-Stream.salam
PipeA
func PipeA(p: PipePair): dyn StreamStream.The example for this item is shown with the ones above: net-tunnel-Stream.salam
PipeB
func PipeB(p: PipePair): dyn StreamStream.The example for this item is shown with the ones above: net-tunnel-Stream.salam
WrapTcp
func WrapTcp(c: tcp.Conn): dyn StreamStream.The example for this item is shown with the ones above: net-tunnel-Wrap.salam
WrapTls
func WrapTls(c: tls.Conn): dyn StreamStream.WrapWebSocket
func WrapWebSocket(c: websocket.Conn): dyn StreamStream: 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 StreamOk.The example for this item is shown with the ones above: net-tunnel-Wrap.salam
WithPrefix
func WithPrefix(s: dyn Stream, prefix: str): dyn StreamThe example for this item is shown with the ones above: net-tunnel-Stream.salam
ReadFull
func ReadFull(s: dyn Stream, buf: void*, n: int): boolThe example for this item is shown with the ones above: net-tunnel-Stream.salam
WriteAll
func WriteAll(s: dyn Stream, data: str): boolThe example for this item is shown with the ones above: net-tunnel-Stream.salam
WriteAllBytes
func WriteAllBytes(s: dyn Stream, ptr: void*, n: int): boolThe example for this item is shown with the ones above: net-tunnel-Stream.salam
ReadByte
func ReadByte(s: dyn Stream): int-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): strThe example for this item is shown with the ones above: net-tunnel-Stream.salam
NewRouterDialer
func NewRouterDialer(router: Router): RouterDialerThe example for this item is shown with the ones above: net-tunnel-RouterDialer.salam
RouterDialerAdd
func RouterDialerAdd(rd &: RouterDialer, tag: str, d: dyn Dialer)The example for this item is shown with the ones above: net-tunnel-RouterDialer.salam
BuildDialer
func BuildDialer(c: Config): RouterDialerRouterDialer 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): intThe example for this item is shown with the ones above: net-tunnel-Mixed.salam
ByteString
func ByteString(b: int): strThe example for this item is shown with the ones above: net-tunnel-Mixed.salam
NewMixed
func NewMixed(): MixedProtocolMixedProtocol. 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)The example for this item is shown with the ones above: net-tunnel-Mixed.salam
MixedSetSocks4
func MixedSetSocks4(m &: MixedProtocol, p: dyn InboundProtocol)The example for this item is shown with the ones above: net-tunnel-Mixed.salam
MixedSetHttp
func MixedSetHttp(m &: MixedProtocol, p: dyn InboundProtocol)The example for this item is shown with the ones above: net-tunnel-Mixed.salam
ListenTcp
func ListenTcp(port: int): TcpInboundOk.The example for this item is shown with the ones above: net-tunnel-Inbound.salam
ListenTcpAt
func ListenTcpAt(host: str, port: int): TcpInboundListenTcp, 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): TcpInboundTcpInbound 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)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)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): ForwardHandlerForwardHandler 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): ForwardHandlerGuardDialer and tells the inbound when the policy refused the target.NewChain
func NewChain(base: dyn Dialer): ChainThe example for this item is shown with the ones above: net-tunnel-Chain.salam
ChainAdd
func ChainAdd(c &: Chain, hop: dyn Connector)The example for this item is shown with the ones above: net-tunnel-Chain.salam
NewFallback
func NewFallback(): FallbackThe example for this item is shown with the ones above: net-tunnel-Dialers.salam
FallbackAdd
func FallbackAdd(f &: Fallback, d: dyn Dialer)The example for this item is shown with the ones above: net-tunnel-Dialers.salam
Direct
func Direct(): dyn DialerDirectDialer as a Dialer.The example for this item is shown with the ones above: net-tunnel-Dialers.salam
Block
func Block(): dyn DialerBlockDialer 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 DialerProxyDialer.The example for this item is shown with the ones above: net-tunnel-Chain.salam
ParseIPv4
func ParseIPv4(s: str): Vector<int>The example for this item is shown with the ones above: net-tunnel-Addr.salam
ParseIPv6
func ParseIPv6(text: str): Vector<int>:: 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>The example for this item is shown with the ones above: net-tunnel-Addr.salam
FormatIP
func FormatIP(b: Vector<int>): str:: form.The example for this item is shown with the ones above: net-tunnel-Addr.salam
IsIP
func IsIP(s: str): boolThe example for this item is shown with the ones above: net-tunnel-Addr.salam
NewAddr
func NewAddr(host: str, port: int): AddrAddr 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): Addrhost: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>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()
endEncodeVless
func EncodeVless(a: Addr): Vector<u8>The example for this item is shown with the ones above: net-tunnel-Wire.salam
DecodeSocks
func DecodeSocks(ptr: void*, n: int): Addrvalid 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): AddrDecodeSocks 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*mem.Free.The example for this item is shown with the ones above: net-tunnel-Wire.salam
ReadSocksAddr
func ReadSocksAddr(s: dyn Stream): AddrThe example for this item is shown with the ones above: net-tunnel-Wire.salam
ReadVlessAddr
func ReadVlessAddr(s: dyn Stream): AddrThe example for this item is shown with the ones above: net-tunnel-Wire.salam
WatchHandshake
func WatchHandshake(fd: i64, total_ms: int): i64WatchDone
func WatchDone(ticket: i64): boolWatchExpired
func WatchExpired(): i64NewDomainSet
func NewDomainSet(): DomainSetThe example for this item is shown with the ones above: net-tunnel-Router.salam
NewIpSet
func NewIpSet(): IpSetThe example for this item is shown with the ones above: net-tunnel-Router.salam
DomainSetFull
func DomainSetFull(s &: DomainSet, host: str)The example for this item is shown with the ones above: net-tunnel-Router.salam
DomainSetSuffix
func DomainSetSuffix(s &: DomainSet, host: str)The example for this item is shown with the ones above: net-tunnel-Router.salam
DomainSetKeyword
func DomainSetKeyword(s &: DomainSet, word: str)The example for this item is shown with the ones above: net-tunnel-Router.salam
DomainSetRegex
func DomainSetRegex(s &: DomainSet, pattern: str): boolThe example for this item is shown with the ones above: net-tunnel-Router.salam
DomainSetContains
func DomainSetContains(s: DomainSet, host: str): boolThe example for this item is shown with the ones above: net-tunnel-Router.salam
IpSetAddNet
func IpSetAddNet(s &: IpSet, net: Vector<int>, prefix: int)The example for this item is shown with the ones above: net-tunnel-Router.salam
IpSetAdd
func IpSetAdd(s &: IpSet, cidr: str): boolThe example for this item is shown with the ones above: net-tunnel-Router.salam
IpSetContains
func IpSetContains(s: IpSet, ip: Vector<int>): boolThe example for this item is shown with the ones above: net-tunnel-Router.salam
ParseDomainList
func ParseDomainList(text: str, attr: str): DomainSetThe example for this item is shown with the ones above: net-tunnel-Router.salam
ParseIpList
func ParseIpList(text: str): IpSetThe example for this item is shown with the ones above: net-tunnel-Router.salam
SetGeoDir
func SetGeoDir(dir: str)The example for this item is shown with the ones above: net-tunnel-Router.salam
RegisterGeoSite
func RegisterGeoSite(name: str, set: DomainSet)The example for this item is shown with the ones above: net-tunnel-Router.salam
RegisterGeoIp
func RegisterGeoIp(name: str, set: IpSet)The example for this item is shown with the ones above: net-tunnel-Router.salam
ClearGeo
func ClearGeo()The example for this item is shown with the ones above: net-tunnel-Router.salam
LookupGeoSite
func LookupGeoSite(spec: str): DomainSetThe example for this item is shown with the ones above: net-tunnel-Router.salam
HasGeoSite
func HasGeoSite(spec: str): boolThe example for this item is shown with the ones above: net-tunnel-Router.salam
LookupGeoIp
func LookupGeoIp(name: str): IpSetThe example for this item is shown with the ones above: net-tunnel-Router.salam
HasGeoIp
func HasGeoIp(name: str): boolThe example for this item is shown with the ones above: net-tunnel-Router.salam
LoadGeoSiteDat
func LoadGeoSiteDat(ptr: void*, n: int, names: Vector<str>): intThe example for this item is shown with the ones above: net-tunnel-Router.salam
LoadGeoIpDat
func LoadGeoIpDat(ptr: void*, n: int, names: Vector<str>): intThe example for this item is shown with the ones above: net-tunnel-Router.salam
LoadGeoSiteFile
func LoadGeoSiteFile(path: str, names: Vector<str>): intThe example for this item is shown with the ones above: net-tunnel-Router.salam
LoadGeoIpFile
func LoadGeoIpFile(path: str, names: Vector<str>): intThe example for this item is shown with the ones above: net-tunnel-Router.salam
NewStats
func NewStats(): StatsThe example for this item is shown with the ones above: net-tunnel-Relay.salam
FreeStats
func FreeStats(s: Stats)The example for this item is shown with the ones above: net-tunnel-Relay.salam
NewIdleTimer
func NewIdleTimer(limit_ms: i64): IdleTimerThe 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): RelayResultThe example for this item is shown with the ones above: net-tunnel-Relay.salam
Relay
func Relay(a: dyn Stream, b: dyn Stream): RelayResultRelayWith 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%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-_.~.The example for this item is shown with the ones above: net-tunnel-Link.salam
B64Decode
func B64Decode(s: str): strThe example for this item is shown with the ones above: net-tunnel-Link.salam
B64EncodeUrl
func B64EncodeUrl(s: str): strThe example for this item is shown with the ones above: net-tunnel-Link.salam
ParseLink
func ParseLink(link: str): Nodevmess://, 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
NodeToLink
func NodeToLink(n: Node): strThe example for this item is shown with the ones above: net-tunnel-Link.salam
ParseSubscription
func ParseSubscription(text: str): Subscription# 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): AddrAddr.The example for this item is shown with the ones above: net-tunnel-Link.salam
NodeLabel
func NodeLabel(n: Node): strprotocol://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)The example for this item is shown with the ones above: net-tunnel-Registry.salam
RegisterInbound
func RegisterInbound(m: dyn InboundMaker)The example for this item is shown with the ones above: net-tunnel-Registry.salam
HasOutbound
func HasOutbound(name: str): boolfreedom and blackhole always are.The example for this item is shown with the ones above: net-tunnel-Registry.salam
HasInbound
func HasInbound(name: str): boolThe example for this item is shown with the ones above: net-tunnel-Registry.salam
CreateOutbound
func CreateOutbound(cfg: OutboundConfig): dyn DialerBlockDialer, 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 InboundProtocolNullProtocol.The example for this item is shown with the ones above: net-tunnel-Registry.salam
ParseConfig
func ParseConfig(text: str): ConfigConfig. 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): ConfigThe example for this item is shown with the ones above: net-tunnel-Config.salam
ValidateConfig
func ValidateConfig(c: Config): Vector<str>The example for this item is shown with the ones above: net-tunnel-Config.salam
ConfigToJson
func ConfigToJson(c: Config): strThe example for this item is shown with the ones above: net-tunnel-Config.salam
BuildRouter
func BuildRouter(c: Config): RouterRouter 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): OutboundConfigNode) 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): NodeOutboundFromNode: 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): Configdirect 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): ConnLimitsLimitsEnter
func LimitsEnter(l: ConnLimits, fd: i64): Admitok is false.LimitsHandshakeDone
func LimitsHandshakeDone(l: ConnLimits, a &: Admit)LimitsRelease
func LimitsRelease(l: ConnLimits, a &: Admit)LimitsActive
func LimitsActive(l: ConnLimits): i64Go
func Go(fn: i64, arg: void*): boolspawn, because a thread that is never joined keeps its stack (8 MB of address space) for good.NewRule
func NewRule(tag: str): RuleThe example for this item is shown with the ones above: net-tunnel-Router.salam
RuleDomain
func RuleDomain(r &: Rule, host: str)The example for this item is shown with the ones above: net-tunnel-Router.salam
RuleSuffix
func RuleSuffix(r &: Rule, suffix: str)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)The example for this item is shown with the ones above: net-tunnel-Router.salam
RuleRegex
func RuleRegex(r &: Rule, pattern: str): boolThe example for this item is shown with the ones above: net-tunnel-Router.salam
ParseCidr
func ParseCidr(text: str): Matcher10.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): boolThe example for this item is shown with the ones above: net-tunnel-Router.salam
RulePrivate
func RulePrivate(r &: Rule)localhost.The example for this item is shown with the ones above: net-tunnel-Router.salam
RulePort
func RulePort(r &: Rule, spec: str): bool80,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): booldomain:, 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): boolgeoip:private.The example for this item is shown with the ones above: net-tunnel-Router.salam
RuleMatches
func RuleMatches(r: Rule, target: Addr): boolThe 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>>): boolThe 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)The example for this item is shown with the ones above: net-tunnel-Router.salam
RuleGeoSite
func RuleGeoSite(r &: Rule, name: str): boolThe example for this item is shown with the ones above: net-tunnel-Router.salam
RuleGeoIp
func RuleGeoIp(r &: Rule, name: str): boolThe example for this item is shown with the ones above: net-tunnel-Router.salam
NewRouter
func NewRouter(default_tag: str): RouterThe example for this item is shown with the ones above: net-tunnel-Router.salam
RouterAdd
func RouterAdd(rt &: Router, r: Rule)The example for this item is shown with the ones above: net-tunnel-Router.salam
ParseRules
func ParseRules(text: str, default_tag: str): RuleSetDOMAIN, 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): RuleSetParseRules.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.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