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_portwebsite looks like any HTTPS site. - Failure is silent. A wrong password, a browser, a scanner: all get the fallback site.
fallbackscounts them. - A
Linkis a connection over TCP, TLS or WebSocket.Dialreturns one; read and write it withLinkReadandLinkWriteStr, and close it withLinkClose.use_wscarries Trojan inside a WebSocket so that a CDN or reverse proxy can sit in front. DialUDPopens a UDP session where every packet names its own target (SendUDP,ReadUDPPacket).
Things that catch people out:
- Use TLS, with a real certificate.
Serveis the real server.ServePlainandServeWShave 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.insecureswitches 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
ServeWSis 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.ReplayCacheand acensor.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.ServePlainandServeWSlisten on127.0.0.1unlessbind_hostsays otherwise; give0.0.0.0only 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
Linkis not atunnel.Stream.Linkis the package's own connection type. Fortunnel.Chainand routing use theConnectorandInboundadapters, 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.
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)
endWith 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.ServePlainandServeWSare 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
::1or::(SetBind), TCP and UDP targets may be IPv6, and loopback tests run over::1. - UDP runs inside the same connection.
ReadUDPPacketgives one packet at a time. - Free what you allocate:
LinkClosefor aLink,FreeSessionfor aSession. - No logging happens by itself.
Types
Connector
struct Connectortunnel.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.| Name | Type | Description |
|---|---|---|
server | tunnel.Addr | The address of the Trojan server. |
password | str = "" | The password. It is sent as a SHA-224 hash. |
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)
endServer
func Server(): tunnel.AddrThe example for this item is shown with the ones above: net-trojan-Adapter.salam
Handshake
func Handshake(s: dyn tunnel.Stream, target: tunnel.Addr): boolThe example for this item is shown with the ones above: net-trojan-Adapter.salam
Name
func Name(): strtrojan.The example for this item is shown with the ones above: net-trojan-Adapter.salam
Inbound
struct Inboundtunnel.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.| Name | Type | Description |
|---|---|---|
hashes | Vector | The accepted password hashes, as 56 character hex text. |
replies | int = 0 | A 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(): strtrojan.The example for this item is shown with the ones above: net-trojan-Adapter.salam
Handshake
func Handshake(s: dyn tunnel.Stream): tunnel.SessionThe 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)The example for this item is shown with the ones above: net-trojan-Adapter.salam
ClientConfig
struct ClientConfigNewClientConfig.| Name | Type | Description |
|---|---|---|
server_host | str = "" | The server's host name or IP. |
server_port | int = 443 | The server's port. The default is 443. |
password | str = "" | The password. It is sent as its SHA-224 hash. |
sni | str = "" | The server name sent in the TLS handshake and checked in the certificate. Empty means the host. |
alpn | str = "" | The ALPN protocol to offer, for example h2 or http/1.1. Empty offers none. |
insecure | bool = false | True to skip the certificate check. For tests only: it removes the protection TLS gives. |
use_tls | bool = true | True to run over TLS, the normal and safe setting. The default is true. With use_ws it controls wss versus ws. |
use_ws | bool = false | True to carry Trojan inside a WebSocket (so a CDN or a reverse proxy can sit in front). The default is false. |
ws_path | str = "/" | The WebSocket path. The default is /. |
lazy_header | bool = false | True 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_ms | int = 10000 | How long connecting may take, in milliseconds. The default is 10000. |
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)
endRequest
struct Request| Name | Type | Description |
|---|---|---|
status | int = 1 | ParseOK, ParseNeedMore or ParseInvalid. Check it first. |
hash | str = "" | The 56 character password hash from the request. |
cmd | int = 0 | CmdConnect or CmdUDP. |
atyp | int = 0 | The address type of the target. |
host | str = "" | The target host name or IP. |
port | int = 0 | The target port. |
consumed | int = 0 | How many bytes the request took. Anything after that is payload. |
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
endUDPPacket
struct UDPPacket| Name | Type | Description |
|---|---|---|
status | int = 1 | ParseOK, ParseNeedMore or ParseInvalid. |
atyp | int = 0 | The address type. |
host | str = "" | The host the datagram is for or came from. |
port | int = 0 | The port. |
payload_off | int = 0 | The offset of the payload in the parsed bytes. |
payload_len | int = 0 | The payload length. |
consumed | int = 0 | How many bytes the packet took in total. |
The example for this item is shown with the ones above: net-trojan-Wire.salam
Link
struct LinkDial returns one; the server makes one for each client. Pass it by reference to the read and write functions.| Name | Type | Description |
|---|---|---|
kind | int = 0 | LinkTCP, LinkTLS or LinkWS. |
tc | tcp.Conn | The TCP connection, when kind is LinkTCP. |
sc | tls.Conn | The TLS connection, when kind is LinkTLS. |
ws | websocket.Conn | The WebSocket connection, when kind is LinkWS. |
rbuf | compress.ByteBuf | Bytes that were read from the peer but not handed out yet. |
roff | int = 0 | How much of rbuf has been handed out. |
ubuf | compress.ByteBuf | Bytes of a UDP packet that is still being assembled. |
pending | compress.ByteBuf | The 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 LinkStreamLink 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.| Name | Type | Description |
|---|---|---|
link | Link* | The Link it wraps. |
/*
* 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)
endRead
func Read(max: int): strmax 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): intmax 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): intThe example for this item is shown with the ones above: net-trojan-Modes.salam
WriteBytes
func WriteBytes(ptr: void*, n: int): intn 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)The example for this item is shown with the ones above: net-trojan-Modes.salam
Shutdown
func Shutdown()The example for this item is shown with the ones above: net-trojan-Modes.salam
CloseWrite
func CloseWrite()Shutdown.The example for this item is shown with the ones above: net-trojan-Modes.salam
Close
func Close()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(): boolThe example for this item is shown with the ones above: net-trojan-Modes.salam
ServerConfig
struct ServerConfigNewServerConfig.| Name | Type | Description |
|---|---|---|
cert_file | str = "" | The PEM certificate file for TLS. Use a certificate for a real domain. |
key_file | str = "" | The PEM private key file. |
hashes | Vector | The accepted password hashes. AddPassword and AddPasswordHash add to it. |
fallback_host | str = "" | The host of the website that gets every connection that is not a valid Trojan client. Empty means 127.0.0.1. |
fallback_port | int = 0 | The port of that website. 0, the default, means there is none and such connections are simply closed. |
max_per_source | int = 32 | How many connections of one address may be in their handshake at once. 0 means no limit. |
handshake_timeout_ms | int = 5000 | How long a client has to send a complete request, in milliseconds. The default is 5000. |
connect_timeout_ms | int = 10000 | How long connecting to the target (or the fallback site) may take. The default is 10000. |
idle_timeout_ms | int = 0 | Cut a connection after this many silent milliseconds once it is running. 0, the default, means never. |
mode | int = 0 | How Listen serves: ModeTLS (the default), ModePlain or ModeWS. |
bind_host | str = "" | 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. |
dialer | dyn tunnel.Dialer | The tunnel.Dialer that reaches targets. Used when has_dialer is true; otherwise targets are dialled directly with connect_timeout_ms. |
has_dialer | bool = false | True after SetDialer or SetRouter. |
stats | tunnel.Stats | The tunnel.Stats that count bytes and connections of this server. Used when has_stats is true. |
has_stats | bool = false | True after SetStats. |
allow_udp | bool = true | True (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. |
policy | tunnel.DestPolicy | Which destinations may be reached. By default loopback, private, link local, metadata and other special addresses are refused. See DestPolicy. |
router | tunnel.RouterDialer | The router dialer set by SetRouter; UDP targets that it sends to a block outbound are dropped. |
has_router | bool = false | True when a router dialer is set. |
max_connections | int = 1024 | The 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_ms | int = 10000 | The 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 SessionHandshake: whether a valid client spoke, what it asked for and the bytes involved.| Name | Type | Description |
|---|---|---|
ok | bool = false | True when the request was valid and its password hash is known. |
req | Request | The parsed request: command and target. |
seen | compress.ByteBuf | Every byte read during the handshake. When ok is false these are what Fallback replays to the website. |
extra | compress.ByteBuf | Payload that arrived in the same read as the request and must go to the target first. |
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)
endServerStats
struct ServerStatsStats returns them.| Name | Type | Description |
|---|---|---|
accepted | i64 = 0 | Connections accepted. |
authed | i64 = 0 | Connections that were valid Trojan clients. |
fallbacks | i64 = 0 | Connections handed to the fallback website. |
The example for this item is shown with the ones above: net-trojan-Client.salam
Server
struct ServerServe, ServePlain and ServeWS return it and Stop ends it.| Name | Type | Description |
|---|---|---|
ok | bool = false | True when the port was bound and the server is running. |
port | int = 0 | The port it listens on, useful when you asked for 0. |
host | str = "" | The address the server listens on. |
st | ServerState* | 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…
cmd.| Name | Value |
|---|---|
CmdConnect | 1 |
CmdUDP | 3 |
Address types Atyp…
atyp.| Name | Value |
|---|---|
AtypIPv4 | 1 |
AtypDomain | 3 |
AtypIPv6 | 4 |
Parse results Parse…
status and status.| Name | Value |
|---|---|
ParseOK | 0 |
ParseNeedMore | 1 |
ParseInvalid | 2 |
Link kinds Link…
kind.| Name | Value |
|---|---|
LinkTCP | 0 |
LinkTLS | 1 |
LinkWS | 2 |
Server modes Mode…
mode: ModeTLS, ModePlain and ModeWS.| Name | Value |
|---|---|
ModeTLS | 0 |
ModePlain | 1 |
ModeWS | 2 |
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): ConnectorConnector 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(): InboundInbound 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)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): ClientConfigThe example for this item is shown with the ones above: net-trojan-Client.salam
Dial
func Dial(cfg: ClientConfig, host: str, port: int): LinkLink 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): LinkSendUDP and ReadUDPPacket.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)
endSendUDP
func SendUDP(l &: Link, host: str, port: int, p: void*, n: int): boolThe example for this item is shown with the ones above: net-trojan-Udp.salam
ParseIPv4
func ParseIPv4(text: str, out &: compress.ByteBuf): boolThe example for this item is shown with the ones above: net-trojan-Wire.salam
ParseIPv6
func ParseIPv6(text: str, out &: compress.ByteBuf): boolThe example for this item is shown with the ones above: net-trojan-Wire.salam
FormatIPv4
func FormatIPv4(p: void*, off: int): strThe example for this item is shown with the ones above: net-trojan-Wire.salam
FormatIPv6
func FormatIPv6(p: void*, off: int): strThe example for this item is shown with the ones above: net-trojan-Wire.salam
EncodeAddr
func EncodeAddr(host: str, port: int): compress.ByteBufThe 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): intThe 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.ByteBufThe 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.ByteBufThe example for this item is shown with the ones above: net-trojan-Wire.salam
ParseRequest
func ParseRequest(p: void*, n: int): Requeststatus; 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.ByteBufThe example for this item is shown with the ones above: net-trojan-Wire.salam
ParseUDPPacket
func ParseUDPPacket(p: void*, n: int): UDPPacketstatus.The example for this item is shown with the ones above: net-trojan-Wire.salam
NewTCPLink
func NewTCPLink(c: tcp.Conn): LinkLink.The example for this item is shown with the ones above: net-trojan-Handshake.salam
NewTLSLink
func NewTLSLink(c: tls.Conn): LinkLink.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)
endNewWSLink
func NewWSLink(c: websocket.Conn): LinkLink.LinkOk
func LinkOk(l: Link): boolThe example for this item is shown with the ones above: net-trojan-Client.salam
LinkFd
func LinkFd(l: Link): i64The example for this item is shown with the ones above: net-trojan-Handshake.salam
LinkRead
func LinkRead(l &: Link, buf: void*, max: int): intThe example for this item is shown with the ones above: net-trojan-Client.salam
LinkWrite
func LinkWrite(l &: Link, p: void*, n: int): boolThe example for this item is shown with the ones above: net-trojan-Client.salam
LinkWriteBuf
func LinkWriteBuf(l &: Link, b: compress.ByteBuf): boolThe example for this item is shown with the ones above: net-trojan-Handshake.salam
LinkWriteStr
func LinkWriteStr(l &: Link, s: str): boolThe example for this item is shown with the ones above: net-trojan-Client.salam
LinkSetTimeout
func LinkSetTimeout(l: Link, millis: int)The example for this item is shown with the ones above: net-trojan-Client.salam
LinkShutdown
func LinkShutdown(l: Link)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)
endLinkClose
func LinkClose(l &: Link)The example for this item is shown with the ones above: net-trojan-Client.salam
Relay
func Relay(a: Link*, b: Link*): i64NewUDPPacket
func NewUDPPacket(): UDPPacketUDPPacket 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): boolThe 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): boolThe example for this item is shown with the ones above: net-trojan-Udp.salam
Sha224
func Sha224(p: void*, n: int): compress.ByteBufThe example for this item is shown with the ones above: net-trojan-Wire.salam
HexOf
func HexOf(b: compress.ByteBuf): strThe example for this item is shown with the ones above: net-trojan-Wire.salam
Sha224Hex
func Sha224Hex(s: str): strThe example for this item is shown with the ones above: net-trojan-Wire.salam
PasswordHash
func PasswordHash(password: str): strThe example for this item is shown with the ones above: net-trojan-Wire.salam
NewServerConfig
func NewServerConfig(cert_file: str, key_file: str): ServerConfigServePlain 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)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)
endAddPassword
func AddPassword(cfg &: ServerConfig, password: str)The example for this item is shown with the ones above: net-trojan-Client.salam
AddPasswordHash
func AddPasswordHash(cfg &: ServerConfig, hash: str)The example for this item is shown with the ones above: net-trojan-Client.salam
Handshake
func Handshake(l &: Link, cfg: ServerConfig): SessionSession. 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)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): boolServeLink
func ServeLink(lp: Link*, cfg: ServerConfig): intAsStream
func AsStream(lp: Link*): dyn tunnel.StreamLink 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)Listen uses.The example for this item is shown with the ones above: net-trojan-Modes.salam
SetBind
func SetBind(cfg &: ServerConfig, host: str)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)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)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)AllowPrivate
func AllowPrivate(cfg &: ServerConfig, enabled: bool)SetStats
func SetStats(cfg &: ServerConfig, st: tunnel.Stats)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): ServerModeTLS, 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): Serverok.The example for this item is shown with the ones above: net-trojan-Tls.salam
ServePlain
func ServePlain(cfg: ServerConfig, port: int): ServerThe example for this item is shown with the ones above: net-trojan-Client.salam
ServeWS
func ServeWS(cfg: ServerConfig, port: int): ServerThe example for this item is shown with the ones above: net-trojan-WebSocket.salam
ServerPort
func ServerPort(s: Server): intThe example for this item is shown with the ones above: net-trojan-Client.salam
Stats
func Stats(s: Server): ServerStatsThe example for this item is shown with the ones above: net-trojan-Client.salam
Stop
func Stop(s: Server)The example for this item is shown with the ones above: net-trojan-Client.salam