Standard library · Network

net.trojan

The Trojan protocol: a TLS client and server that look like an ordinary HTTPS site, a fallback website for everyone who is not a client, WebSocket mode, UDP and a connector for proxy chains.

import net.trojan

Trojan is a proxy protocol built to look like nothing. A client opens a normal TLS connection to a server and sends one line that holds a password hash and the target it wants; if the hash is right the server connects there and relays bytes. If the first bytes are not a valid Trojan request, the server does not complain. It hands the connection, bytes and all, to an ordinary website, so a visitor or a probe gets a real page from a real site. This package has both sides: Dial is the client and Serve runs the server. It also plugs into net.tunnel as an Inbound and a Connector for routing and proxy chains. To see how the pieces fit together in an app, read Building a v2ray style or VPN app.

Core ideas:

  • A password is never sent. What goes on the wire is its SHA-224 hash (PasswordHash), as 56 hex characters. The server keeps hashes (AddPassword, AddPasswordHash).
  • TLS does the hiding. The Trojan request is inside the TLS stream, and a server with a valid certificate and a real fallback_port website looks like any HTTPS site.
  • Failure is silent. A wrong password, a browser, a scanner: all get the fallback site. fallbacks counts them.
  • A Link is a connection over TCP, TLS or WebSocket. Dial returns one; read and write it with LinkRead and LinkWriteStr, and close it with LinkClose. use_ws carries Trojan inside a WebSocket so that a CDN or reverse proxy can sit in front.
  • DialUDP opens a UDP session where every packet names its own target (SendUDP, ReadUDPPacket).

Things that catch people out:

  • Use TLS, with a real certificate. Serve is the real server. ServePlain and ServeWS have no TLS: the password hash and all traffic are readable by anyone on the path, and the hash can be replayed. Use them on loopback, in tests, or behind a reverse proxy that ends TLS. insecure switches certificate checking off; never use it outside a test.
  • The fallback site must be real. A fallback that is missing, empty or obviously fake makes the server easy to recognise. Without fallback_port, a failed client is just disconnected, which is itself a sign. Serve real content, on the same machine, with the same certificate name.
  • WebSocket mode has no fallback. A failed client of ServeWS is simply closed. Let the reverse proxy in front decide what the outside world sees.
  • No replay protection. The request holds no nonce, so anyone who can read it (a plain connection, a broken certificate check) can replay it. net.censor has a censor.ReplayCache and a censor.ProbeGuard, but this package does not use them for you.
  • Where a server listens. Serve (ModeTLS) listens on every IPv4 interface because it is the public face of the server, so use long random passwords. ServePlain and ServeWS listen on 127.0.0.1 unless bind_host says otherwise; give 0.0.0.0 only when something you control reaches them from outside.
  • The password and its hash are equivalent. Whoever has the hash can authenticate. Protect the config like a password file.
  • A Link is not a tunnel.Stream. Link is the package's own connection type. For tunnel.Chain and routing use the Connector and Inbound adapters, which work on streams.

A server, a client and a fallback

This runs the whole idea on loopback with no certificate: a Trojan server in front of an echo server, with a web server as the fallback. The real client gets the echo; anyone else sends ordinary web traffic to the same port and gets the web page. In production Serve adds TLS in front of exactly this behaviour.

net-trojan-guide-loopback.salam
import mem
import net.tcp
import net.trojan
import sync

mut echo_port := 0
mut web_port := 0
mut web_seen := ""
mut ready := sync.NewWaitGroup()

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

func web_server():
    srv := tcp.Bind(0)
    web_port = tcp.Port(srv)
    sync.Done(ready)
    until true:
        c := tcp.Accept(srv)
        web_seen = tcp.Read(c, 4096)
        tcp.Write(c, "HTTP/1.1 200 OK\r\nContent-Length: 12\r\nConnection: close\r\n\r\nhello probes")
        tcp.Close(c)
    end
end

func start_servers():
    sync.Add(ready, 2 as i64)
    _a := spawn(echo_server)
    _b := spawn(web_server)
    sync.Wait(ready)
end

func text_of(p: void*, n: int): str:
    buf := mem.Allocate((n + 1) as u64)
    mem.Copy(buf, p, n as u64)
    (buf as u8*)[n] = 0 as u8
    ret buf as str
end

func read_text(l &: trojan.Link, want: int): str:
    buf := mem.Allocate(8192 as u64)
    defer mem.Free(buf)
    mut out := ""
    until out.len() < want:
        n := trojan.LinkRead(l, buf, 8192)
        if n <= 0:
            break
        end
        out += text_of(buf, n)
    end
    ret out
end

func main:
    start_servers()
    mut config := trojan.NewServerConfig("", "")
    trojan.AllowPrivate(config, true)
    trojan.AddPassword(config, "secret")
    trojan.SetFallback(config, "127.0.0.1", web_port)
    server := trojan.ServePlain(config, 0)

    mut client := trojan.NewClientConfig("127.0.0.1", server.port, "secret")
    client.use_tls = false
    mut link := trojan.Dial(client, "127.0.0.1", echo_port)
    trojan.LinkWriteStr(link, "through trojan")
    println read_text(link, 19)
    trojan.LinkClose(link)

    stranger := tcp.Dial("127.0.0.1", server.port)
    tcp.Write(stranger, "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
    println tcp.Read(stranger, 4096).substr(0, 15)
    tcp.Close(stranger)
    trojan.Stop(server)
end
Outputecho:through trojan HTTP/1.1 200 OK

With TLS, give the config the certificate and the key and start it with Serve. Point SetFallback at a real web server on the same machine, and use the domain of the certificate as the name clients connect to.

mut config := trojan.NewServerConfig("fullchain.pem", "privkey.pem")
trojan.AddPassword(config, "a long random password")
trojan.SetFallback(config, "127.0.0.1", 8080)
server := trojan.Serve(config, 443)

Using Trojan in a proxy chain

For routing and chains, a Connector writes the Trojan request over any stream that already reaches the server, and an Inbound reads it on the server side. The stream underneath decides what is private: over a plain TCP stream the hop is readable, so put TLS under it. With a tunnel.Dialer that provides TLS as the upstream, tunnel.Via makes a complete outbound:

hop := trojan.NewConnector("proxy.example.com", 443, "a long random password")
outbound := tunnel.Via(your_tls_dialer, hop)

The guide of net.tunnel shows the whole pipeline with this connector in it. For a client that does not need routing, Dial does the TLS itself and is simpler.

What to know

  • Use Serve, with a real certificate and a real fallback website. ServePlain and ServeWS are for loopback, tests and use behind something that ends TLS.
  • No replay protection and no brute force limit of its own. Use long random passwords.
  • The server listens on every IPv4 interface in TLS mode and on loopback in plain and WebSocket mode; change it with SetBind.
  • IPv6 works: the server can listen on ::1 or :: (SetBind), TCP and UDP targets may be IPv6, and loopback tests run over ::1.
  • UDP runs inside the same connection. ReadUDPPacket gives one packet at a time.
  • Free what you allocate: LinkClose for a Link, FreeSession for a Session.
  • No logging happens by itself.

Types

Connector

struct Connector
One Trojan hop of a tunnel.Chain: over a stream that already reaches the Trojan server it writes the Trojan request for a target. Make one with NewConnector. The stream underneath must already be encrypted (TLS) for the hop to be private.
NameTypeDescription
servertunnel.AddrThe address of the Trojan server.
passwordstr = ""The password. It is sent as a SHA-224 hash.
net-trojan-Adapter.salam
import mem
import net.trojan
import net.tunnel
import compress

func text_of(p: void*, n: int): str:
    buf := mem.Allocate((n + 1) as u64)
    mem.Copy(buf, p, n as u64)
    (buf as u8*)[n] = 0 as u8
    ret buf as str
end

func main:
    hop := trojan.NewConnector("vpn.example.com", 443, "password")
    println hop.Name(), hop.Server().host, hop.Server().port

    pair := tunnel.NewPipe()
    println hop.Handshake(tunnel.PipeA(pair), tunnel.NewAddr("example.com", 443))
    mut expected := trojan.EncodeRequest("password", trojan.CmdConnect, "example.com", 443)
    buf := mem.Allocate(256 as u64)
    n := tunnel.PipeB(pair).ReadBytes(buf, 256)
    println n, n == expected.len, text_of(buf, n) == text_of(expected.data, expected.len)

    mut inbound := trojan.NewInbound()
    trojan.InboundAddPassword(inbound, "password")
    trojan.InboundAddPassword(inbound, "other")
    println inbound.Name(), inbound.hashes.len(), inbound.replies

    good := tunnel.NewPipe()
    tunnel.PipeB(good).Write(text_of(expected.data, expected.len) + "payload")
    session := inbound.Handshake(tunnel.PipeA(good))
    println session.ok, session.target.host, session.target.port, session.protocol, session.command, session.initial

    web := tunnel.NewPipe()
    tunnel.PipeB(web).Write("GET / HTTP/1.1\r\nHost: x\r\n\r\n")
    stranger := inbound.Handshake(tunnel.PipeA(web))
    println stranger.ok, stranger.error, stranger.initial.len()

    wrong := trojan.EncodeRequest("nope", trojan.CmdConnect, "example.com", 443)
    bad := tunnel.NewPipe()
    tunnel.PipeB(bad).Write(text_of(wrong.data, wrong.len))
    rejected := inbound.Handshake(tunnel.PipeA(bad))
    println rejected.ok, rejected.initial.len() == wrong.len
    inbound.Reply(tunnel.PipeA(bad), rejected, false)

    mut chain := tunnel.NewChain(tunnel.Direct())
    tunnel.ChainAdd(chain, hop)
    println chain.Name()
    compress.BufFree(expected)
end
Outputtrojan vpn.example.com 443 true 76 true true trojan 2 0 true example.com 443 trojan 1 payload false trojan authentication failed 27 false true chain>trojan

Server

func Server(): tunnel.Addr
The address of the Trojan server.

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

Handshake

func Handshake(s: dyn tunnel.Stream, target: tunnel.Addr): bool
Writes the Trojan request (hash, connect command, target) on the open stream. It does not wait for an answer, because Trojan has none: data can follow at once.

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

Name

func Name(): str
Returns trojan.

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

Inbound

struct Inbound
The server side of Trojan for tunnel.InboundProtocol: it reads the request from a stream, checks the password hash and returns the target. A request that fails the check is not answered, and the bytes read so far are kept in tunnel.initial, so a caller can pass them to a decoy site. Make one with NewInbound.
NameTypeDescription
hashesVectorThe accepted password hashes, as 56 character hex text.
repliesint = 0A counter kept by the adapter. Trojan sends no reply, so it stays 0.

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

Name

func Name(): str
Returns trojan.

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

Handshake

func Handshake(s: dyn tunnel.Stream): tunnel.Session
Reads until a whole request has arrived, then checks its hash against the known ones. Returns the target, command and any payload that followed the request, or a failed session that still carries what was read.

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

Reply

func Reply(_s: dyn tunnel.Stream, _sess: tunnel.Session, _ok: bool)
Does nothing: Trojan has no reply.

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

ClientConfig

struct ClientConfig
How to reach a Trojan server: its address and password, the TLS options and the transport. Make it with NewClientConfig.
NameTypeDescription
server_hoststr = ""The server's host name or IP.
server_portint = 443The server's port. The default is 443.
passwordstr = ""The password. It is sent as its SHA-224 hash.
snistr = ""The server name sent in the TLS handshake and checked in the certificate. Empty means the host.
alpnstr = ""The ALPN protocol to offer, for example h2 or http/1.1. Empty offers none.
insecurebool = falseTrue to skip the certificate check. For tests only: it removes the protection TLS gives.
use_tlsbool = trueTrue to run over TLS, the normal and safe setting. The default is true. With use_ws it controls wss versus ws.
use_wsbool = falseTrue to carry Trojan inside a WebSocket (so a CDN or a reverse proxy can sit in front). The default is false.
ws_pathstr = "/"The WebSocket path. The default is /.
lazy_headerbool = falseTrue to hold the Trojan request back and send it together with the first data you write, so the first packet is not a fixed size request. The default is false.
timeout_msint = 10000How long connecting may take, in milliseconds. The default is 10000.
net-trojan-Client.salam
import mem
import net.tcp
import net.trojan
import sync

mut echo_port := 0
mut web_port := 0
mut web_seen := ""
mut ready := sync.NewWaitGroup()

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

func web_server():
    srv := tcp.Bind(0)
    web_port = tcp.Port(srv)
    sync.Done(ready)
    until true:
        c := tcp.Accept(srv)
        web_seen = tcp.Read(c, 4096)
        tcp.Write(c, "HTTP/1.1 200 OK\r\nContent-Length: 12\r\nConnection: close\r\n\r\nhello probes")
        tcp.Close(c)
    end
end

func start_servers():
    sync.Add(ready, 2 as i64)
    _a := spawn(echo_server)
    _b := spawn(web_server)
    sync.Wait(ready)
end

func text_of(p: void*, n: int): str:
    buf := mem.Allocate((n + 1) as u64)
    mem.Copy(buf, p, n as u64)
    (buf as u8*)[n] = 0 as u8
    ret buf as str
end

func read_text(l &: trojan.Link, want: int): str:
    buf := mem.Allocate(8192 as u64)
    defer mem.Free(buf)
    mut out := ""
    until out.len() < want:
        n := trojan.LinkRead(l, buf, 8192)
        if n <= 0:
            break
        end
        out = out + text_of(buf, n)
    end
    ret out
end

func main:
    start_servers()

    mut config := trojan.NewServerConfig("", "")
    trojan.AllowPrivate(config, true)
    trojan.AddPassword(config, "secret")
    trojan.AddPasswordHash(config, trojan.PasswordHash("second"))
    println config.hashes.len(), config.handshake_timeout_ms
    server := trojan.ServePlain(config, 0)
    println "server up:", server.ok, trojan.ServerPort(server) == server.port

    mut client := trojan.NewClientConfig("127.0.0.1", server.port, "secret")
    client.use_tls = false
    println client.server_port != 443, client.use_ws, client.lazy_header, client.timeout_ms
    mut link := trojan.Dial(client, "127.0.0.1", echo_port)
    println "dial ok:", trojan.LinkOk(link), link.kind == trojan.LinkTCP
    println trojan.LinkWriteStr(link, "hello trojan")
    println read_text(link, 17)
    big := mem.Allocate(30000 as u64)
    mem.Set(big, 97, 30000 as u64)
    println trojan.LinkWrite(link, big, 30000)
    mut total := 0
    until total < 30005:
        buf := mem.Allocate(8192 as u64)
        n := trojan.LinkRead(link, buf, 8192)
        mem.Free(buf)
        if n <= 0:
            break
        end
        total += n
    end
    println "echoed", total
    mem.Free(big)
    trojan.LinkSetTimeout(link, 1000)
    trojan.LinkClose(link)

    mut second := trojan.NewClientConfig("127.0.0.1", server.port, "second")
    second.use_tls = false
    second.lazy_header = true
    mut lazy := trojan.Dial(second, "127.0.0.1", echo_port)
    println trojan.LinkWriteStr(lazy, "lazy header")
    println read_text(lazy, 16)
    trojan.LinkClose(lazy)

    sync.SleepMs(300)
    stats := trojan.Stats(server)
    println "stats:", stats.accepted, stats.authed, stats.fallbacks
    trojan.Stop(server)
end
Output2 5000 server up: true true true false false 10000 dial ok: true true true echo:hello trojan true echoed 30040 true echo:lazy header stats: 2 2 0

Request

struct Request
A parsed Trojan request: password hash, command and target.
NameTypeDescription
statusint = 1ParseOK, ParseNeedMore or ParseInvalid. Check it first.
hashstr = ""The 56 character password hash from the request.
cmdint = 0CmdConnect or CmdUDP.
atypint = 0The address type of the target.
hoststr = ""The target host name or IP.
portint = 0The target port.
consumedint = 0How many bytes the request took. Anything after that is payload.
net-trojan-Wire.salam
import net.trojan
import compress

func main:
    println trojan.Sha224Hex("")
    println trojan.Sha224Hex("abc")
    println trojan.PasswordHash("password")
    digest := trojan.Sha224("abc" as void*, 3)
    println digest.len, trojan.HexOf(digest) == trojan.Sha224Hex("abc")

    request := trojan.EncodeRequest("password", trojan.CmdConnect, "example.com", 443)
    println trojan.HexOf(request)
    parsed := trojan.ParseRequest(request.data, request.len)
    println parsed.status == trojan.ParseOK, parsed.cmd == trojan.CmdConnect, parsed.atyp == trojan.AtypDomain, parsed.host, parsed.port, parsed.consumed
    println parsed.hash == trojan.PasswordHash("password")
    same := trojan.EncodeRequestHash(trojan.PasswordHash("password"), trojan.CmdConnect, "example.com", 443)
    println trojan.HexOf(same) == trojan.HexOf(request)

    println "short:", trojan.ParseRequest(request.data, request.len - 3).status == trojan.ParseNeedMore
    junk := trojan.ParseRequest("GET / HTTP/1.1\r\nHost: a\r\n\r\n" as void*, 28)
    println "junk:", junk.status == trojan.ParseInvalid

    udp_request := trojan.EncodeRequest("password", trojan.CmdUDP, "2001:db8::1", 53)
    p6 := trojan.ParseRequest(udp_request.data, udp_request.len)
    println p6.cmd == trojan.CmdUDP, p6.atyp == trojan.AtypIPv6, p6.host, p6.port

    packet := trojan.EncodeUDPPacket("8.8.8.8", 53, "hi" as void*, 2)
    println trojan.HexOf(packet)
    up := trojan.ParseUDPPacket(packet.data, packet.len)
    println up.status == trojan.ParseOK, up.atyp == trojan.AtypIPv4, up.host, up.port, up.payload_off, up.payload_len, up.consumed

    addr := trojan.EncodeAddr("example.com", 443)
    println trojan.HexOf(addr)
    mut atyp := 0
    mut host := ""
    mut port := 0
    println trojan.ParseAddr(addr.data, addr.len, 0, atyp, host, port), atyp, host, port

    mut ip4 := compress.NewByteBuf()
    println trojan.ParseIPv4("192.168.1.20", ip4), trojan.FormatIPv4(ip4.data, 0)
    mut ip6 := compress.NewByteBuf()
    println trojan.ParseIPv6("2001:db8::1", ip6), trojan.FormatIPv6(ip6.data, 0)
    println trojan.CmdConnect, trojan.CmdUDP, trojan.AtypIPv4, trojan.AtypIPv6, trojan.ParseNeedMore, trojan.ParseInvalid
end
Outputd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f 23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7 d63dc919e201d7bc4c825630d2cf25fdc93d4b2f0d46706d29038d01 28 true 64363364633931396532303164376263346338323536333064326366323566646339336434623266306434363730366432393033386430310d0a01030b6578616d706c652e636f6d01bb0d0a true true true example.com 443 76 true true short: true junk: true true true 2001:db8::1 53 0108080808003500020d0a6869 true true 8.8.8.8 53 11 2 13 030b6578616d706c652e636f6d01bb 15 3 example.com 443 true 192.168.1.20 true 2001:db8::1 1 3 1 4 1 2

UDPPacket

struct UDPPacket
A parsed Trojan UDP packet: its target and where the payload sits.
NameTypeDescription
statusint = 1ParseOK, ParseNeedMore or ParseInvalid.
atypint = 0The address type.
hoststr = ""The host the datagram is for or came from.
portint = 0The port.
payload_offint = 0The offset of the payload in the parsed bytes.
payload_lenint = 0The payload length.
consumedint = 0How many bytes the packet took in total.

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

struct Link
One connection to or from a Trojan peer, over TCP, TLS or WebSocket, with the buffers Trojan needs. Dial returns one; the server makes one for each client. Pass it by reference to the read and write functions.
NameTypeDescription
kindint = 0LinkTCP, LinkTLS or LinkWS.
tctcp.ConnThe TCP connection, when kind is LinkTCP.
sctls.ConnThe TLS connection, when kind is LinkTLS.
wswebsocket.ConnThe WebSocket connection, when kind is LinkWS.
rbufcompress.ByteBufBytes that were read from the peer but not handed out yet.
roffint = 0How much of rbuf has been handed out.
ubufcompress.ByteBufBytes of a UDP packet that is still being assembled.
pendingcompress.ByteBufThe Trojan request held back by lazy_header until the first write.

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

LinkStream

struct LinkStream
A Link seen as a tunnel.Stream, so a Trojan connection can be relayed, routed and counted with the net.tunnel tools. Make one with AsStream. Closing it only shuts the connection down; LinkClose still releases the Link.
NameTypeDescription
linkLink*The Link it wraps.
net-trojan-Modes.salam
/*
 * Salam Programming Language (2024-2026)
 *
 *   +-------------------+
 *   |     S A L A M     |
 *   +-------------------+
 *
 * Designed by Seyyed Ali Mohammadiyeh and the Salam Team
 * Born from a decade of language design experience (since 2018)
 *
 * Repository: https://github.com/SalamLang/Salam
 *
 */

import mem
import net.tcp
import net.udp
import net.trojan
import net.tunnel
import compress
import sync

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

func text_of(p: void*, n: int): str:
    buf := mem.Allocate((n + 1) as u64)
    mem.Copy(buf, p, n as u64)
    (buf as u8*)[n] = 0 as u8
    ret buf as str
end

func echo_server():
    srv := tcp.Bind(0)
    echo_port = tcp.Port(srv)
    sync.Done(ready)
    until true:
        c := tcp.Accept(srv)
        until true:
            s := tcp.Read(c, 4096)
            if s.len() == 0:
                break
            end
            _n := tcp.Write(c, s)
        end
        tcp.Close(c)
    end
end

func udp_server():
    s := udp.Bind(0)
    udp_port = udp.Port(s)
    sync.Done(ready)
    until true:
        p := udp.Receive(s, 2048)
        if p.data.len() > 0:
            _n := udp.Send(s, p.from_host, p.from_port, "echo:" + p.data)
        end
    end
end

func read_all_text(l &: trojan.Link, want: int): str:
    buf := mem.Allocate(8192 as u64)
    defer mem.Free(buf)
    mut out := ""
    until out.len() < want:
        n := trojan.LinkRead(l, buf, 8192)
        if n <= 0:
            break
        end
        out = out + text_of(buf, n)
    end
    ret out
end

func main:
    sync.Add(ready, 2 as i64)
    _t1 := spawn(echo_server)
    _t2 := spawn(udp_server)
    sync.Wait(ready)

    mut cfg := trojan.NewServerConfig("", "")
    trojan.AllowPrivate(cfg, true)
    trojan.AddPassword(cfg, "secret")
    trojan.SetMode(cfg, trojan.ModePlain)
    stats := tunnel.NewStats()
    trojan.SetStats(cfg, stats)
    srv := trojan.Listen(cfg, 0)
    println "listen ok", srv.ok, srv.host

    mut ccfg := trojan.NewClientConfig("127.0.0.1", srv.port, "secret")
    ccfg.use_tls = false

    mut ulink := trojan.DialUDP(ccfg)
    println trojan.SendUDP(ulink, "127.0.0.1", udp_port, "one" as void*, 3)
    println trojan.SendUDP(ulink, "127.0.0.1", udp_port, "two" as void*, 3)
    mut pkt := trojan.NewUDPPacket()
    mut payload := compress.NewByteBuf()
    println trojan.ReadUDPPacket(ulink, pkt, payload), text_of(payload.data, payload.len)
    println trojan.ReadUDPPacket(ulink, pkt, payload), text_of(payload.data, payload.len)
    trojan.LinkClose(ulink)

    mut link := trojan.Dial(ccfg, "127.0.0.1", echo_port)
    println trojan.LinkWriteStr(link, "hello stats")
    println read_all_text(link, 11)
    trojan.LinkClose(link)
    sync.SleepMs(300)
    println "up", stats.Up() >= 17, "down", stats.Down() >= 17, "total", stats.Total()
    trojan.Stop(srv)

    mut rt := tunnel.NewRouter("direct")
    mut rule := tunnel.NewRule("block")
    println tunnel.RulePort(rule, "" + echo_port)
    tunnel.RouterAdd(rt, rule)
    mut rd := tunnel.NewRouterDialer(rt)
    tunnel.RouterDialerAdd(rd, "block", tunnel.Block())
    mut cfg2 := trojan.NewServerConfig("", "")
    trojan.AllowPrivate(cfg2, true)
    trojan.AddPassword(cfg2, "secret")
    trojan.SetRouter(cfg2, rd)
    trojan.SetBind(cfg2, "127.0.0.1")
    trojan.SetMode(cfg2, trojan.ModePlain)
    srv2 := trojan.Listen(cfg2, 0)
    mut c2 := trojan.NewClientConfig("127.0.0.1", srv2.port, "secret")
    c2.use_tls = false
    mut blocked := trojan.Dial(c2, "127.0.0.1", echo_port)
    println trojan.LinkWriteStr(blocked, "x")
    println "blocked read", read_all_text(blocked, 1).len()
    trojan.LinkClose(blocked)

    trojan.Stop(srv2)
end
Outputlisten ok true 127.0.0.1 true true true echo:one true echo:two true hello stats up true down true total 2 true true blocked read 0

Read

func Read(max: int): str
Reads up to max bytes as text.

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

ReadBytes

func ReadBytes(buf: void*, max: int): int
Reads up to max bytes into a buffer and returns how many.

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

Write

func Write(data: str): int
Writes text to the link and returns its length, or -1 on failure.

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

WriteBytes

func WriteBytes(ptr: void*, n: int): int
Writes n bytes and returns n, or -1 on failure.

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

SetDeadline

func SetDeadline(millis: int)
Sets the read timeout in milliseconds; 0 means none.

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

Shutdown

func Shutdown()
Shuts the connection down in both directions.

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

CloseWrite

func CloseWrite()
The half-close hook that relays call when one side has finished sending. A Trojan link cannot close only one direction, so it shuts the connection down in both, the same as Shutdown.

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

Close

func Close()
Same as Shutdown: the owner of the Link closes it with LinkClose.

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

Ok

func Ok(): bool
True while the link is open.

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

ServerConfig

struct ServerConfig
The settings of a Trojan server: certificate and key, the accepted passwords, the website to show to everyone else and the time limits. Start from NewServerConfig.
NameTypeDescription
cert_filestr = ""The PEM certificate file for TLS. Use a certificate for a real domain.
key_filestr = ""The PEM private key file.
hashesVectorThe accepted password hashes. AddPassword and AddPasswordHash add to it.
fallback_hoststr = ""The host of the website that gets every connection that is not a valid Trojan client. Empty means 127.0.0.1.
fallback_portint = 0The port of that website. 0, the default, means there is none and such connections are simply closed.
max_per_sourceint = 32How many connections of one address may be in their handshake at once. 0 means no limit.
handshake_timeout_msint = 5000How long a client has to send a complete request, in milliseconds. The default is 5000.
connect_timeout_msint = 10000How long connecting to the target (or the fallback site) may take. The default is 10000.
idle_timeout_msint = 0Cut a connection after this many silent milliseconds once it is running. 0, the default, means never.
modeint = 0How Listen serves: ModeTLS (the default), ModePlain or ModeWS.
bind_hoststr = ""The address to listen on. Empty picks a safe default for the mode: every interface for ModeTLS, 127.0.0.1 for ModePlain and ModeWS, which are meant to sit behind something that ends TLS. Give 0.0.0.0 to listen everywhere on purpose.
dialerdyn tunnel.DialerThe tunnel.Dialer that reaches targets. Used when has_dialer is true; otherwise targets are dialled directly with connect_timeout_ms.
has_dialerbool = falseTrue after SetDialer or SetRouter.
statstunnel.StatsThe tunnel.Stats that count bytes and connections of this server. Used when has_stats is true.
has_statsbool = falseTrue after SetStats.
allow_udpbool = trueTrue (the default) to accept UDP sessions. UDP is always sent directly and does not go through the dialer, so set this to false when a router must be able to block everything.
policytunnel.DestPolicyWhich destinations may be reached. By default loopback, private, link local, metadata and other special addresses are refused. See DestPolicy.
routertunnel.RouterDialerThe router dialer set by SetRouter; UDP targets that it sends to a block outbound are dropped.
has_routerbool = falseTrue when a router dialer is set.
max_connectionsint = 1024The most connections served at once; more are closed (SOCKS) or answered with 503 (HTTP). The default is 1024; 0 turns the cap off.
handshake_deadline_msint = 10000The total time a connection has to finish its handshake, in milliseconds, however slowly it sends. The default is 10000; 0 turns it off.

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

Session

struct Session
The result of Handshake: whether a valid client spoke, what it asked for and the bytes involved.
NameTypeDescription
okbool = falseTrue when the request was valid and its password hash is known.
reqRequestThe parsed request: command and target.
seencompress.ByteBufEvery byte read during the handshake. When ok is false these are what Fallback replays to the website.
extracompress.ByteBufPayload that arrived in the same read as the request and must go to the target first.
net-trojan-Handshake.salam
import net.tcp
import net.trojan
import sync

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

mut config := trojan.NewServerConfig("", "")
trojan.AllowPrivate(config, true)

func serve_one():
    listener := tcp.Bind(0)
    port = tcp.Port(listener)
    sync.Done(ready)
    c := tcp.Accept(listener)
    mut link := trojan.NewTCPLink(c)
    mut session := trojan.Handshake(link, config)
    println "server saw a valid client:", session.ok
    println session.req.host, session.req.port, session.req.cmd == trojan.CmdConnect
    println "bytes read:", session.seen.len, "payload after the request:", session.extra.len
    trojan.FreeSession(session)
    trojan.LinkClose(link)
end

func main:
    trojan.AddPassword(config, "secret")
    sync.Add(ready, 1 as i64)
    t := spawn(serve_one)
    sync.Wait(ready)

    conn := tcp.Dial("127.0.0.1", port)
    request := trojan.EncodeRequest("secret", trojan.CmdConnect, "example.com", 443)
    mut link := trojan.NewTCPLink(conn)
    trojan.LinkWriteBuf(link, request)
    println trojan.LinkOk(link), trojan.LinkFd(link) > 0
    join(t)
    trojan.LinkClose(link)
end
Outputtrue true server saw a valid client: true example.com 443 true bytes read: 76 payload after the request: 0

ServerStats

struct ServerStats
Counters of a running server. Stats returns them.
NameTypeDescription
acceptedi64 = 0Connections accepted.
authedi64 = 0Connections that were valid Trojan clients.
fallbacksi64 = 0Connections handed to the fallback website.

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

Server

struct Server
A running Trojan server. Serve, ServePlain and ServeWS return it and Stop ends it.
NameTypeDescription
okbool = falseTrue when the port was bound and the server is running.
portint = 0The port it listens on, useful when you asked for 0.
hoststr = ""The address the server listens on.
stServerState*Internal state: the stop flag and the counters.

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

Constants and variables

Commands Cmd…

The commands of cmd.
NameValue
CmdConnect1
CmdUDP3

Address types Atyp…

The address types of atyp.
NameValue
AtypIPv41
AtypDomain3
AtypIPv64

Parse results Parse…

The values of status and status.
NameValue
ParseOK0
ParseNeedMore1
ParseInvalid2
The values of kind.
NameValue
LinkTCP0
LinkTLS1
LinkWS2

Server modes Mode…

The values of mode: ModeTLS, ModePlain and ModeWS.
NameValue
ModeTLS0
ModePlain1
ModeWS2

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

Functions

NewConnector

func NewConnector(server_host: str, server_port: int, password: str): Connector
Makes a Connector for a Trojan server address and a password.

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

NewInbound

func NewInbound(): Inbound
Returns an Inbound with no passwords. Add them with InboundAddPassword.

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

InboundAddPassword

func InboundAddPassword(inb &: Inbound, password: str)
Adds a password (stored as its hash) that the inbound accepts.

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

NewClientConfig

func NewClientConfig(server_host: str, server_port: int, password: str): ClientConfig
Makes a config for a server host, port and password with TLS on and the other options at their defaults.

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

Dial

func Dial(cfg: ClientConfig, host: str, port: int): Link
Connects to the Trojan server and sends the request for a target host and port. Returns a Link to read and write; check LinkOk. With lazy_header the request waits for your first write.

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

DialUDP

func DialUDP(cfg: ClientConfig): Link
Connects and starts a UDP session: every packet then carries its own target. Use SendUDP and ReadUDPPacket.
net-trojan-Udp.salam
import mem
import net.trojan
import net.udp
import sync
import compress

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

func udp_server():
    s := udp.Bind(0)
    udp_port = udp.Port(s)
    sync.Done(ready)
    until true:
        p := udp.Receive(s, 2048)
        if p.data.len() > 0:
            udp.Send(s, p.from_host, p.from_port, "echo:" + p.data)
        end
    end
end

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

    mut config := trojan.NewServerConfig("", "")
    trojan.AllowPrivate(config, true)
    trojan.AddPassword(config, "secret")
    server := trojan.ServePlain(config, 0)

    mut client := trojan.NewClientConfig("127.0.0.1", server.port, "secret")
    client.use_tls = false
    mut link := trojan.DialUDP(client)
    println "udp session:", trojan.LinkOk(link)
    println trojan.WriteUDPPacket(link, "127.0.0.1", udp_port, "ping" as void*, 4)

    mut packet := trojan.NewUDPPacket()
    mut payload := compress.NewByteBuf()
    println trojan.ReadUDPPacket(link, packet, payload)
    println packet.host, packet.port == udp_port, packet.status
    buf := mem.Allocate((payload.len + 1) as u64)
    mem.Copy(buf, payload.data, payload.len as u64)
    (buf as u8*)[payload.len] = 0 as u8
    println buf as str
    trojan.LinkClose(link)
    trojan.Stop(server)
end
Outputudp session: true true true 127.0.0.1 true 0 echo:ping

SendUDP

func SendUDP(l &: Link, host: str, port: int, p: void*, n: int): bool
Sends one datagram to a host and port through a UDP session.

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

ParseIPv4

func ParseIPv4(text: str, out &: compress.ByteBuf): bool
Parses IPv4 text into four bytes appended to a buffer. False when it is not an address.

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

ParseIPv6

func ParseIPv6(text: str, out &: compress.ByteBuf): bool
Parses IPv6 text into sixteen bytes appended to a buffer. False when it is not an address.

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

FormatIPv4

func FormatIPv4(p: void*, off: int): str
Formats four bytes at an offset as dotted text.

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

FormatIPv6

func FormatIPv6(p: void*, off: int): str
Formats sixteen bytes at an offset as IPv6 text.

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

EncodeAddr

func EncodeAddr(host: str, port: int): compress.ByteBuf
Writes a host and port in the Trojan (SOCKS5 style) address form. An IP gets its numeric type, anything else is a name.

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

ParseAddr

func ParseAddr(p: void*, n: int, off: int, atyp &: int, host &: str, port &: int): int
Reads an address from memory at an offset: fills in its type, host and port and returns how many bytes it took, 0 when more is needed and -1 when it is invalid.

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

EncodeRequestHash

func EncodeRequestHash(hash: str, cmd: int, host: str, port: int): compress.ByteBuf
Writes a Trojan request from an already computed hash: the 56 hex characters, a line break, the command, the address and a line break.

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

EncodeRequest

func EncodeRequest(password: str, cmd: int, host: str, port: int): compress.ByteBuf
Writes a Trojan request for a password, a command and a target.

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

ParseRequest

func ParseRequest(p: void*, n: int): Request
Parses a Trojan request from memory. Check status; on success consumed says where the payload starts.

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

EncodeUDPPacket

func EncodeUDPPacket(host: str, port: int, payload: void*, n: int): compress.ByteBuf
Writes one UDP packet: the target address, the payload length, a line break and the payload.

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

ParseUDPPacket

func ParseUDPPacket(p: void*, n: int): UDPPacket
Parses one UDP packet from memory. Check status.

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

func NewTCPLink(c: tcp.Conn): Link
Wraps an accepted or dialed TCP connection as a Link.

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

func NewTLSLink(c: tls.Conn): Link
Wraps a TLS connection as a Link.
net-trojan-Tls.salam
import net.trojan

func main:
    mut config := trojan.NewServerConfig("fullchain.pem", "privkey.pem")
    trojan.AddPassword(config, "a long random password")
    trojan.SetFallback(config, "127.0.0.1", 8080)
    server := trojan.Serve(config, 443)
    println "trojan up:", server.ok

    mut client := trojan.NewClientConfig("proxy.example.com", 443, "a long random password")
    client.sni = "proxy.example.com"
    client.alpn = "h2"
    client.lazy_header = true
    mut link := trojan.Dial(client, "example.com", 443)
    println trojan.LinkOk(link), link.kind == trojan.LinkTLS
    trojan.LinkClose(link)
    trojan.Stop(server)
end
func NewWSLink(c: websocket.Conn): Link
Wraps a WebSocket connection as a Link.

LinkOk

func LinkOk(l: Link): bool
True when the link is usable.

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

LinkFd

func LinkFd(l: Link): i64
Returns the file descriptor under the link.

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

LinkRead

func LinkRead(l &: Link, buf: void*, max: int): int
Reads up to a number of bytes into a buffer, first from what was already buffered. Returns how many, or 0 or less at the end or on error.

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

LinkWrite

func LinkWrite(l &: Link, p: void*, n: int): bool
Writes bytes from memory. If a Trojan request is being held back it goes out first. False on failure.

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

LinkWriteBuf

func LinkWriteBuf(l &: Link, b: compress.ByteBuf): bool
Writes a whole byte buffer.

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

LinkWriteStr

func LinkWriteStr(l &: Link, s: str): bool
Writes a text.

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

LinkSetTimeout

func LinkSetTimeout(l: Link, millis: int)
Sets the read and write timeout in milliseconds. 0 removes it.

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

LinkShutdown

func LinkShutdown(l: Link)
Stops both directions so that a thread blocked in a read wakes up, without freeing the link.
net-trojan-WebSocket.salam
import mem
import net.tcp
import net.trojan
import sync

mut echo_port := 0
mut web_port := 0
mut web_seen := ""
mut ready := sync.NewWaitGroup()

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

func web_server():
    srv := tcp.Bind(0)
    web_port = tcp.Port(srv)
    sync.Done(ready)
    until true:
        c := tcp.Accept(srv)
        web_seen = tcp.Read(c, 4096)
        tcp.Write(c, "HTTP/1.1 200 OK\r\nContent-Length: 12\r\nConnection: close\r\n\r\nhello probes")
        tcp.Close(c)
    end
end

func start_servers():
    sync.Add(ready, 2 as i64)
    _a := spawn(echo_server)
    _b := spawn(web_server)
    sync.Wait(ready)
end

func text_of(p: void*, n: int): str:
    buf := mem.Allocate((n + 1) as u64)
    mem.Copy(buf, p, n as u64)
    (buf as u8*)[n] = 0 as u8
    ret buf as str
end

func read_text(l &: trojan.Link, want: int): str:
    buf := mem.Allocate(8192 as u64)
    defer mem.Free(buf)
    mut out := ""
    until out.len() < want:
        n := trojan.LinkRead(l, buf, 8192)
        if n <= 0:
            break
        end
        out = out + text_of(buf, n)
    end
    ret out
end

func main:
    start_servers()
    mut config := trojan.NewServerConfig("", "")
    trojan.AllowPrivate(config, true)
    trojan.AddPassword(config, "wspass")
    server := trojan.ServeWS(config, 0)
    println "ws server up:", server.ok

    mut client := trojan.NewClientConfig("127.0.0.1", server.port, "wspass")
    client.use_tls = false
    client.use_ws = true
    client.ws_path = "/trojan"
    mut link := trojan.Dial(client, "127.0.0.1", echo_port)
    println "ws dial ok:", trojan.LinkOk(link), link.kind == trojan.LinkWS
    trojan.LinkWriteStr(link, "over websocket")
    println read_text(link, 19)
    trojan.LinkShutdown(link)
    trojan.LinkClose(link)
    trojan.Stop(server)
end
Outputws server up: true ws dial ok: true true echo:over websocket

LinkClose

func LinkClose(l &: Link)
Closes the link.

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

Relay

func Relay(a: Link*, b: Link*): i64
Copies data both ways between two links until one ends and returns how many bytes moved in total.

NewUDPPacket

func NewUDPPacket(): UDPPacket
Returns an empty UDPPacket to be filled by ReadUDPPacket.

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

ReadUDPPacket

func ReadUDPPacket(l &: Link, pkt &: UDPPacket, payload &: compress.ByteBuf): bool
Reads one UDP packet from a link: fills in the packet's target and puts the payload bytes in a buffer. False at the end of the link or on a malformed packet.

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

WriteUDPPacket

func WriteUDPPacket(l &: Link, host: str, port: int, p: void*, n: int): bool
Writes one UDP packet for a host and port to a link.

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

Sha224

func Sha224(p: void*, n: int): compress.ByteBuf
Returns the SHA-224 hash (28 bytes) of some memory.

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

HexOf

func HexOf(b: compress.ByteBuf): str
Returns the bytes of a buffer as lowercase hex.

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

Sha224Hex

func Sha224Hex(s: str): str
Returns the SHA-224 hash of a text as 56 hex characters.

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

PasswordHash

func PasswordHash(password: str): str
Returns the hash Trojan puts on the wire for a password: its SHA-224 as 56 hex characters.

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

NewServerConfig

func NewServerConfig(cert_file: str, key_file: str): ServerConfig
Makes a config from a certificate file and a key file. Empty names are fine for ServePlain and ServeWS, which use no TLS.

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

SetFallback

func SetFallback(cfg &: ServerConfig, host: str, port: int)
Sets the website that receives every connection that is not a valid client. Point it at a real site (your own, on the same machine) so that a probe sees an ordinary web server.
net-trojan-Fallback.salam
import mem
import net.tcp
import net.trojan
import sync

mut echo_port := 0
mut web_port := 0
mut web_seen := ""
mut ready := sync.NewWaitGroup()

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

func web_server():
    srv := tcp.Bind(0)
    web_port = tcp.Port(srv)
    sync.Done(ready)
    until true:
        c := tcp.Accept(srv)
        web_seen = tcp.Read(c, 4096)
        tcp.Write(c, "HTTP/1.1 200 OK\r\nContent-Length: 12\r\nConnection: close\r\n\r\nhello probes")
        tcp.Close(c)
    end
end

func start_servers():
    sync.Add(ready, 2 as i64)
    _a := spawn(echo_server)
    _b := spawn(web_server)
    sync.Wait(ready)
end

func text_of(p: void*, n: int): str:
    buf := mem.Allocate((n + 1) as u64)
    mem.Copy(buf, p, n as u64)
    (buf as u8*)[n] = 0 as u8
    ret buf as str
end

func read_text(l &: trojan.Link, want: int): str:
    buf := mem.Allocate(8192 as u64)
    defer mem.Free(buf)
    mut out := ""
    until out.len() < want:
        n := trojan.LinkRead(l, buf, 8192)
        if n <= 0:
            break
        end
        out = out + text_of(buf, n)
    end
    ret out
end

func main:
    start_servers()

    mut config := trojan.NewServerConfig("", "")
    trojan.AllowPrivate(config, true)
    trojan.AddPassword(config, "secret")
    trojan.SetFallback(config, "127.0.0.1", web_port)
    println config.fallback_host, config.fallback_port == web_port
    server := trojan.ServePlain(config, 0)

    mut wrong := trojan.NewClientConfig("127.0.0.1", server.port, "not the password")
    wrong.use_tls = false
    mut stranger := trojan.Dial(wrong, "127.0.0.1", echo_port)
    answer := read_text(stranger, 40)
    println "wrong password gets a web page:", answer.starts_with("HTTP/1.1 200 OK"), answer.includes("hello probes")
    trojan.LinkClose(stranger)

    probe := tcp.Dial("127.0.0.1", server.port)
    tcp.Write(probe, "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
    page := tcp.Read(probe, 4096)
    println "plain web request gets the page too:", page.includes("hello probes")
    tcp.Close(probe)
    println "the site saw:", web_seen.substr(0, 14)

    mut right := trojan.NewClientConfig("127.0.0.1", server.port, "secret")
    right.use_tls = false
    mut member := trojan.Dial(right, "127.0.0.1", echo_port)
    trojan.LinkWriteStr(member, "real client")
    println read_text(member, 16)
    trojan.LinkClose(member)

    sync.SleepMs(300)
    stats := trojan.Stats(server)
    println "accepted", stats.accepted, "authed", stats.authed, "fallbacks", stats.fallbacks
    trojan.Stop(server)
end
Output127.0.0.1 true wrong password gets a web page: true true plain web request gets the page too: true the site saw: GET / HTTP/1.1 echo:real client accepted 3 authed 1 fallbacks 2

AddPassword

func AddPassword(cfg &: ServerConfig, password: str)
Adds a password the server accepts. Only its SHA-224 hash is kept.

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

AddPasswordHash

func AddPasswordHash(cfg &: ServerConfig, hash: str)
Adds an already hashed password (56 hex characters), so the plain password never has to be in the config.

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

Handshake

func Handshake(l &: Link, cfg: ServerConfig): Session
Reads the client's first bytes until a whole request has arrived, checks the password hash and returns a Session. It never answers the client.

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

FreeSession

func FreeSession(s &: Session)
Frees the buffers of a session.

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

Fallback

func Fallback(lp: Link*, cfg: ServerConfig, seen: compress.ByteBuf): bool
Connects to the fallback website, replays the bytes the client already sent and then relays both ways, so the client talks to the real site. False when there is no fallback or it cannot be reached.
func ServeLink(lp: Link*, cfg: ServerConfig): int
Serves one client on a link: handshake, then relay to the target, a UDP session, or the fallback. Returns 1 when a valid client was served, 2 when the handshake failed and 3 when the target could not be reached. A WebSocket link is not sent to the fallback.

AsStream

func AsStream(lp: Link*): dyn tunnel.Stream
Wraps a Link as a tunnel.Stream.

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

SetMode

func SetMode(cfg &: ServerConfig, mode: int)
Chooses the mode Listen uses.

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

SetBind

func SetBind(cfg &: ServerConfig, host: str)
Sets the address Listen binds; see bind_host.

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

SetDialer

func SetDialer(cfg &: ServerConfig, d: dyn tunnel.Dialer)
Sends the connections of the server through a tunnel.Dialer, for example a chain or another proxy, instead of dialling directly.

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

SetRouter

func SetRouter(cfg &: ServerConfig, rd: tunnel.RouterDialer)
Sends the connections of the server through a tunnel.RouterDialer, so rules decide per target whether it goes direct, through a proxy or is blocked.

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

SetPolicy

func SetPolicy(cfg &: ServerConfig, p: tunnel.DestPolicy)
Sets the destination policy of the server.

AllowPrivate

func AllowPrivate(cfg &: ServerConfig, enabled: bool)
Lets the server reach loopback and private addresses. It only takes effect when the server is bound to loopback; on any other address it is ignored.

SetStats

func SetStats(cfg &: ServerConfig, st: tunnel.Stats)
Counts bytes up and down, connections, active and failed ones into a tunnel.Stats that you keep.

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

Listen

func Listen(cfg: ServerConfig, port: int): Server
Starts the server in the mode of the config (ModeTLS, ModePlain or ModeWS) on the configured address and a port (0 picks a free one). Serve, ServePlain and ServeWS are the same call with the mode fixed. Check ok.

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

Serve

func Serve(cfg: ServerConfig, port: int): Server
Starts a Trojan server on a port (0 picks a free one) with TLS, using the certificate and key of the config. This is the normal way to run one. Check ok.

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

ServePlain

func ServePlain(cfg: ServerConfig, port: int): Server
Starts a server without TLS. Anyone on the path can read the traffic, so use it only on loopback, in tests, or behind something else that provides TLS.

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

ServeWS

func ServeWS(cfg: ServerConfig, port: int): Server
Starts a server that speaks Trojan inside WebSocket without TLS, meant to sit behind a reverse proxy or CDN that ends TLS. There is no fallback site in this mode.

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

ServerPort

func ServerPort(s: Server): int
Returns the port of a server.

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

Stats

func Stats(s: Server): ServerStats
Returns the counters of a server: accepted, authenticated and handed to the fallback.

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

Stop

func Stop(s: Server)
Stops accepting new clients. Connections already running finish on their own.

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