Standard library · Network

net.socks

SOCKS4, SOCKS4a and SOCKS5: a client with login, BIND and UDP associate, a server with users, access rules and a pluggable way to reach targets, and a connector for proxy chains.

import net.socks

SOCKS is the plain, widely supported proxy protocol: a program asks a proxy to connect somewhere on its behalf and then talks through the connection as if it were direct. This package has both sides. Connect is the client; Start runs a server. It also plugs into net.tunnel, as a Socks5Inbound or Socks4Inbound that can share one port with an HTTP proxy, and as a SocksConnector that is one hop of a proxy chain. To see how the pieces fit together in an app, read Building a v2ray style or VPN app.

Core ideas:

  • A client is a value: NewClient or NewClientAuth fills a Client, and Connect returns a ConnectResult with a stream. Failure is a value too: ok is false and code and err say why.
  • SOCKS5 is the default. Set version to Socks4 or Socks4a for older servers. SOCKS4 sends an IPv4 address; SOCKS4a lets the server resolve a name.
  • A name sent to a SOCKS5 or SOCKS4a proxy is resolved by the proxy, so the lookup does not happen on your machine.
  • A server is a ServerConfig given to Start. How it reaches targets is a callback (SetConnector) or a tunnel.Dialer (SetDialer), which is how it is joined to routing rules and proxy chains.
  • Associate opens a UDP relay and Bind waits for an incoming connection. Both are SOCKS5 features the server can switch off.

Things that catch people out:

  • A server listens on bind_host, which is 127.0.0.1 by default. Set it to 0.0.0.0 (or an address) only on purpose. A SOCKS server without a login that is reachable from outside is an open proxy anyone can abuse. Require a user (AddUser), restrict it with a firewall, or put it behind a tunnel.
  • The login is not encrypted. SOCKS5 user name and password travel in clear text. Use it on a local port or through an encrypted tunnel, or set tls when the proxy is reachable over TLS. The server side of this package speaks plain TCP only.
  • SOCKS4 has no passwords. A server that requires logins refuses SOCKS4 clients, and allow_v4 can switch SOCKS4 off.
  • allow_bind is off by default because BIND makes the proxy open a listening port for the client, and a UDP relay does the same with a UDP port. Those sockets are bound to bind_host too, and that is the address reported to the client.
  • UDP has no fragments. Only fragment 0 is supported, and there is no UDP over SOCKS4.
  • IPv6 works end to end. TCP CONNECT, BIND and the UDP relay carry IPv6 targets (address type 4), and the server can listen on ::1 or on :: for both families. A UDP relay bound to an IPv4 address still reaches IPv6 targets through a second outbound socket. SOCKS4 stays IPv4.
  • Close streams. A stream is an open connection. Close it when you are done.

Using a SOCKS proxy from a program

Make a client for the proxy, connect to a target, and use the stream. The proxy here is a local server so the example runs anywhere; in real use it is the address of the proxy you trust.

net-socks-guide-client.salam
import net.socks
import net.tcp

func echo_conn(arg: i64):
    c := tcp.ConnFromFd(arg)
    until true:
        data := tcp.Read(c, 4096)
        if data.len() == 0:
            break
        end
        tcp.Write(c, data)
    end
    tcp.Close(c)
end

func echo_accept(arg: i64):
    srv := tcp.Server { fd = arg }
    until true:
        c := tcp.Accept(srv)
        if not tcp.ConnOk(c):
            break
        end
        socks.Go(echo_conn, c.fd as void*)
    end
end

func start_echo(): int:
    listener := tcp.Bind(0)
    socks.Go(echo_accept, listener.fd as void*)
    ret tcp.Port(listener)
end

func main:
    target := start_echo()
    mut proxy_cfg := socks.NewServerConfig(0)
    socks.AllowPrivate(proxy_cfg, true)
    proxy := socks.Start(proxy_cfg)

    client := socks.NewClient("127.0.0.1", proxy.port)
    result := socks.Connect(client, "127.0.0.1", target)
    if not result.ok:
        println "the proxy said no:", result.code, result.err
        ret
    end
    result.stream.Write("hello from behind the proxy")
    println result.stream.Read(100)
    result.stream.Close()
    socks.Stop(proxy)
end
Outputhello from behind the proxy

DialConn does the same but returns a plain TCP connection for code that already uses net.tcp. Through a proxy that needs a login use NewClientAuth. To go through two proxies in a row, see tunnel.Chain and the chain example below.

Running a SOCKS server

NewServerConfig gives a config for a port; AddUser turns the login on; Start binds and returns at once, and Stop ends it. How the server reaches a target is the part you usually change: leave DirectConnect for a plain proxy, set a callback with SetConnector to route by name, or give it a tunnel.Dialer with SetDialer to use rules, chains and block lists from net.tunnel. allow can refuse commands, hosts or ports before any connection is made.

To put SOCKS on the same port as an HTTP proxy, give a Socks5Inbound to a tunnel.MixedProtocol instead of calling Start. Handle serves a single stream you accepted yourself.

mut config := socks.NewServerConfig(1080)
socks.AddUser(config, "alice", "a long random password")
server := socks.Start(config)
if not server.ok:
    println "port 1080 is busy"
end

What to know

  • The server listens on bind_host (loopback by default). Use a login or a firewall, never an open proxy.
  • The login is plain text. SOCKS4 has no login at all.
  • Names are resolved by the proxy for SOCKS5 and SOCKS4a, and by the client for SOCKS4.
  • Start runs the accept loop on its own thread; connections run on further threads. Stop stops new clients only.
  • The decoders (DecodeRequest, DecodeReply, Decode4Request, DecodeAddress) return the bytes used, 0 for "need more data" and -1 for invalid, so they work on a growing buffer.
  • No logging happens by itself.

Types

Client

struct Client
A SOCKS client: which proxy to use, which version, and the credentials. Make it with NewClient or NewClientAuth.
NameTypeDescription
versionint = 5Socks5, Socks4 or Socks4a. The default is SOCKS5.
hoststr = ""The proxy server's host name or IP.
portint = 0The proxy server's port.
usernamestr = ""The user name for SOCKS5 (or the user id field of SOCKS4).
passwordstr = ""The password for SOCKS5. Sent only when a user name is set.
timeout_msint = 10000How long connecting and the handshake may take, in milliseconds. The default is 10000.
tlsbool = falseTrue to wrap the connection to the proxy in TLS first (SOCKS over TLS). The default is false.
server_namestr = ""The name to send and check in the proxy's certificate when tls is on. Empty means the host.
insecurebool = falseTrue to skip the certificate check. For tests only.
net-socks-Connect.salam
import net.socks
import net.tcp

func echo_conn(arg: i64):
    c := tcp.ConnFromFd(arg)
    until true:
        data := tcp.Read(c, 4096)
        if data.len() == 0:
            break
        end
        tcp.Write(c, data)
    end
    tcp.Close(c)
end

func echo_accept(arg: i64):
    srv := tcp.Server { fd = arg }
    until true:
        c := tcp.Accept(srv)
        if not tcp.ConnOk(c):
            break
        end
        socks.Go(echo_conn, c.fd as void*)
    end
end

func start_echo(): int:
    listener := tcp.Bind(0)
    socks.Go(echo_accept, listener.fd as void*)
    ret tcp.Port(listener)
end

func say(label: str, r: socks.ConnectResult):
    println label + ": ok=" + r.ok + " code=" + r.code + " " + r.err
end

func main:
    echo := start_echo()
    mut server_cfg := socks.NewServerConfig(0)
    socks.AllowPrivate(server_cfg, true)
    server := socks.Start(server_cfg)
    println "server up:", server.ok

    client := socks.NewClient("127.0.0.1", server.port)
    println client.version == socks.Socks5, client.timeout_ms, client.tls
    r := socks.Connect(client, "127.0.0.1", echo)
    say("connect", r)
    r.stream.Write("hello through socks5")
    println r.stream.Read(100)
    r.stream.Close()

    by_name := socks.Connect(client, "localhost", echo)
    by_name.stream.Write("by name")
    println by_name.stream.Read(100)
    by_name.stream.Close()

    conn := socks.DialConn(client, "127.0.0.1", echo)
    tcp.Write(conn, "plain tcp conn")
    println tcp.Read(conn, 100)
    tcp.Close(conn)

    say("refused", socks.Connect(client, "127.0.0.1", tcp.FreePort()))
    say("no such host", socks.Connect(client, "no-such-host.invalid", 80))
    socks.Stop(server)
end
Outputserver up: true true 10000 false connect: ok=true code=0 hello through socks5 by name plain tcp conn refused: ok=false code=5 connection refused no such host: ok=false code=4 host unreachable

HandshakeResult

struct HandshakeResult
The outcome of a SOCKS command on an open stream.
NameTypeDescription
okbool = falseTrue when the server accepted the request.
codeint = 0The reply code, see RepSucceeded and the Rep4Granted family.
errstr = ""A readable reason when it failed.
boundAddressThe address the server reports: the one it bound (for BIND) or the one it connected from.
net-socks-Chain.salam
import net.socks
import net.tcp
import net.tunnel

func echo_conn(arg: i64):
    c := tcp.ConnFromFd(arg)
    until true:
        data := tcp.Read(c, 4096)
        if data.len() == 0:
            break
        end
        tcp.Write(c, data)
    end
    tcp.Close(c)
end

func echo_accept(arg: i64):
    srv := tcp.Server { fd = arg }
    until true:
        c := tcp.Accept(srv)
        if not tcp.ConnOk(c):
            break
        end
        socks.Go(echo_conn, c.fd as void*)
    end
end

func start_echo(): int:
    listener := tcp.Bind(0)
    socks.Go(echo_accept, listener.fd as void*)
    ret tcp.Port(listener)
end

func main:
    echo := start_echo()
    mut first_cfg := socks.NewServerConfig(0)
    socks.AllowPrivate(first_cfg, true)
    first := socks.Start(first_cfg)
    mut second_cfg := socks.NewServerConfig(0)
    socks.AllowPrivate(second_cfg, true)
    second := socks.Start(second_cfg)

    hop := socks.Connect(socks.NewClient("127.0.0.1", first.port), "127.0.0.1", second.port)
    println "first hop:", hop.ok
    onward := socks.ClientHandshake(hop.stream, socks.NewClient("", 0), socks.CmdConnect, "127.0.0.1", echo)
    println "second hop:", onward.ok
    hop.stream.Write("through two proxies")
    println hop.stream.Read(100)
    hop.stream.Close()

    mut chain := tunnel.NewChain(tunnel.Direct())
    tunnel.ChainAdd(chain, socks.NewConnector(socks.NewClient("127.0.0.1", first.port)))
    tunnel.ChainAdd(chain, socks.NewConnector(socks.NewClient("127.0.0.1", second.port)))
    println chain.Name()
    s := chain.Dial(tunnel.NewAddr("127.0.0.1", echo))
    s.Write("same thing with a chain")
    println s.Read(100)
    s.Close()

    connector := socks.NewConnector(socks.NewClient("127.0.0.1", first.port))
    println connector.Name(), connector.Server().String() == "127.0.0.1:" + first.port
    socks.Stop(first)
    socks.Stop(second)
end
Outputfirst hop: true second hop: true through two proxies chain>socks5>socks5 same thing with a chain socks5 true

ConnectResult

struct ConnectResult
The outcome of Connect or Bind: the stream when it worked, and the reason when it did not.
NameTypeDescription
okbool = falseTrue when the proxy connected you to the target.
codeint = 0The reply code.
errstr = ""A readable reason when it failed.
streamdyn tunnel.StreamThe open stream to the target (through the proxy). Valid when ok is true.
boundAddressThe address the proxy reports.

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

UDPSession

struct UDPSession
A SOCKS5 UDP association: a TCP control connection plus a UDP socket aimed at the proxy's relay. Make it with Associate.
NameTypeDescription
okbool = falseTrue when the association was made.
errstr = ""A readable reason when it was not.
controldyn tunnel.StreamThe TCP connection that keeps the association alive. Closing it ends the association.
sockudp.SocketThe UDP socket used to send and receive datagrams.
relay_hoststr = ""The relay address the proxy gave.
relay_portint = 0The relay port the proxy gave.
net-socks-Udp.salam
import mem
import net.socks
import net.udp
import str

func udp_echo(arg: i64):
    s := udp.Socket { fd = arg }
    buf := mem.Allocate(2048 as u64)
    until true:
        mut host := ""
        mut port := 0
        n := udp.ReceiveBytes(s, buf, 2048, host, port)
        if n < 0:
            break
        end
        udp.SendBytes(s, host, port, buf, n)
    end
end

func main:
    echo := udp.Bind(0)
    echo_port := udp.Port(echo)
    socks.Go(udp_echo, echo.fd as void*)

    mut server_cfg := socks.NewServerConfig(0)
    socks.AllowPrivate(server_cfg, true)
    server := socks.Start(server_cfg)
    client := socks.NewClient("127.0.0.1", server.port)
    session := socks.Associate(client)
    println "associated:", session.ok, session.relay_host
    socks.UDPSetTimeout(session, 2000)
    println "sent:", socks.UDPSend(session, "127.0.0.1", echo_port, "datagram one")

    buf := mem.Allocate(1024 as u64)
    mut from := socks.Address {}
    n := socks.UDPReceiveBytes(session, buf, 1024, from)
    println "received:", n, "from", from.host, from.port == echo_port
    (buf as u8*)[n] = 0 as u8
    println str.Clone(buf as str)
    println "bytes sent:", socks.UDPSendBytes(session, "127.0.0.1", echo_port, "raw" as void*, 3)
    mem.Free(buf)
    socks.CloseUDP(session)

    mut four := socks.NewClient("127.0.0.1", server.port)
    four.version = socks.Socks4
    println "socks4 has no udp:", socks.Associate(four).ok
    socks.Stop(server)
end
Outputassociated: true 127.0.0.1 sent: 12 received: 12 from 127.0.0.1 true datagram one bytes sent: 3 socks4 has no udp: false

Outbound

struct Outbound
What a connect callback returns: whether the target was reached, the reply code to send back and the stream.
NameTypeDescription
okbool = falseTrue when the target was reached.
codeint = 1The SOCKS reply code to give the client. The default is 1, general failure.
errstr = ""A readable reason, for logs.
streamdyn tunnel.StreamThe stream to the target when ok is true.
boundAddressThe local address of the outgoing connection, reported back to the client.
net-socks-Policy.salam
import net.socks
import net.tcp
import net.tunnel

func echo_conn(arg: i64):
    c := tcp.ConnFromFd(arg)
    until true:
        data := tcp.Read(c, 4096)
        if data.len() == 0:
            break
        end
        tcp.Write(c, data)
    end
    tcp.Close(c)
end

func echo_accept(arg: i64):
    srv := tcp.Server { fd = arg }
    until true:
        c := tcp.Accept(srv)
        if not tcp.ConnOk(c):
            break
        end
        socks.Go(echo_conn, c.fd as void*)
    end
end

func start_echo(): int:
    listener := tcp.Bind(0)
    socks.Go(echo_accept, listener.fd as void*)
    ret tcp.Port(listener)
end

func say(label: str, r: socks.ConnectResult):
    println label + ": ok=" + r.ok + " code=" + r.code + " " + r.err
end

func main:
    echo := start_echo()

    mut routed := socks.NewServerConfig(0)
    socks.AllowPrivate(routed, true)
    socks.SetConnector(routed, (host: str, _port: int): if host == "echo.internal": ret socks.DirectConnect("127.0.0.1", echo, 2000) end ret socks.Outbound { ok = false, code = socks.RepNetUnreachable, err = "no route" } end)
    routed_server := socks.Start(routed)
    client := socks.NewClient("127.0.0.1", routed_server.port)
    say("known name", socks.Connect(client, "echo.internal", 1))
    say("unknown name", socks.Connect(client, "other.internal", 1))
    socks.Stop(routed_server)

    mut acl := socks.NewServerConfig(0)
    socks.AllowPrivate(acl, true)
    acl.allow = (cmd: int, _host: str, port: int): ret not (cmd == socks.CmdConnect and port == 25) end
    acl_server := socks.Start(acl)
    acl_client := socks.NewClient("127.0.0.1", acl_server.port)
    say("allowed", socks.Connect(acl_client, "127.0.0.1", echo))
    say("port 25", socks.Connect(acl_client, "127.0.0.1", 25))
    socks.Stop(acl_server)

    mut blocked := socks.NewServerConfig(0)
    socks.AllowPrivate(blocked, true)
    socks.SetDialer(blocked, tunnel.Block())
    blocked_server := socks.Start(blocked)
    say("block dialer", socks.Connect(socks.NewClient("127.0.0.1", blocked_server.port), "127.0.0.1", echo))
    socks.Stop(blocked_server)

    mut direct := socks.NewServerConfig(0)
    socks.AllowPrivate(direct, true)
    socks.SetDialer(direct, tunnel.Direct())
    direct_server := socks.Start(direct)
    say("direct dialer", socks.Connect(socks.NewClient("127.0.0.1", direct_server.port), "127.0.0.1", echo))
    socks.Stop(direct_server)
end
Outputknown name: ok=true code=0 unknown name: ok=false code=3 network unreachable allowed: ok=true code=0 port 25: ok=false code=2 connection not allowed by ruleset block dialer: ok=false code=4 host unreachable direct dialer: ok=true code=0

RelayStats

struct RelayStats
How many bytes Relay moved in each direction.
NameTypeDescription
a_to_bi64 = 0Bytes from the first stream to the second.
b_to_ai64 = 0Bytes from the second stream to the first.
net-socks-Relay.salam
import net.socks
import net.tunnel
import pool

func job(a: dyn tunnel.Stream, b: dyn tunnel.Stream):
    stats := socks.Relay(a, b, 2000)
    println "relayed", stats.a_to_b, stats.b_to_a
end

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

    workers := pool.NewElastic(1 as i64, 2 as i64, 2 as i64)
    pool.Submit(workers, (): job(left, right) end)
    client.Write("hello")
    println server.Read(100)
    server.Write("world!")
    println client.Read(100)
    client.Close()
    pool.Wait(workers)
    pool.Stop(workers)
end
Outputhello world! relayed 5 6

Address

struct Address
A host and port as SOCKS sees them: the address type, the text of the host and the port.
NameTypeDescription
kindint = 0AtypIPv4, AtypDomain or AtypIPv6.
hoststr = ""The host name or IP text.
portint = 0The port.
net-socks-Codec.salam
import net.socks

func main:
    methods := Vector {} as Vector<u8>
    methods.push(0 as u8)
    methods.push(2 as u8)
    println "greeting   ", socks.ToHex(socks.EncodeGreeting(methods))
    println "choice     ", socks.ToHex(socks.EncodeMethodChoice(socks.AuthUserPass))
    println "auth       ", socks.ToHex(socks.EncodeAuthRequest("alice", "pw"))
    println "auth ok    ", socks.ToHex(socks.EncodeAuthReply(true))

    target := socks.NewAddress("example.com", 443)
    println target.kind == socks.AtypDomain, socks.ClassifyHost("10.1.2.3") == socks.AtypIPv4, socks.ClassifyHost("::1") == socks.AtypIPv6
    request := socks.EncodeRequest(socks.CmdConnect, target)
    println "request    ", socks.ToHex(request)
    mut parsed := socks.Request {}
    println socks.DecodeRequest(request, parsed), parsed.cmd == socks.CmdConnect, parsed.addr.host, parsed.addr.port
    mut partial := Vector {} as Vector<u8>
    repeat 5 in i:
        partial.push(request[i])
    end
    println "short      ", socks.DecodeRequest(partial, parsed)

    reply := socks.EncodeReply(socks.RepSucceeded, socks.NewAddress("10.0.0.1", 1080))
    println "reply      ", socks.ToHex(reply)
    mut answer := socks.Reply {}
    println socks.DecodeReply(reply, answer), answer.code, answer.addr.host, socks.ReplyText(answer.code)
    println socks.ReplyText(socks.RepConnRefused), "|", socks.ReplyText(socks.Rep4Rejected)

    mut ip := Vector {} as Vector<u8>
    println socks.ParseIPv4("192.168.1.20", ip), socks.FormatIPv4(ip, 0)
    mut ip6 := Vector {} as Vector<u8>
    println socks.ParseIPv6("2001:db8::1", ip6), socks.FormatIPv6(ip6, 0)
    encoded := socks.EncodeAddress(target)
    mut back := socks.Address {}
    println socks.DecodeAddress(encoded, 0, back), back.host

    udp := socks.EncodeUDPHeader(target, 0)
    println "udp header ", socks.ToHex(udp)
    mut header := socks.UDPHeader {}
    println socks.DecodeUDPHeader(udp, header), header.frag, header.addr.host, header.length

    four := socks.Encode4Request(socks.CmdConnect, "127.0.0.1", 80, "bob")
    println "socks4 req ", socks.ToHex(four)
    mut four_req := socks.Request {}
    println socks.Decode4Request(four, four_req), four_req.user, four_req.addr.port
    four_reply := socks.Encode4Reply(socks.Rep4Granted, "127.0.0.1", 80)
    println "socks4 rep ", socks.ToHex(four_reply)
    mut four_ans := socks.Reply {}
    println socks.Decode4Reply(four_reply, four_ans), four_ans.code
    println socks.VerSocks5, socks.VerSocks4, socks.AuthNone, socks.AuthNoAcceptable, socks.CmdBind, socks.CmdUDPAssociate, socks.AtypIPv4
    println socks.RepGeneralFailure, socks.RepNotAllowed, socks.Socks4a
end
Outputgreeting 05020002 choice 0502 auth 0105616c696365027077 auth ok 0100 true true true request 050100030b6578616d706c652e636f6d01bb 18 true example.com 443 short 0 reply 050000010a0000010438 10 0 10.0.0.1 succeeded connection refused | request rejected or failed true 192.168.1.20 true 2001:db8::1 15 example.com udp header 000000030b6578616d706c652e636f6d01bb true 0 example.com 18 socks4 req 040100507f000001626f6200 12 bob 80 socks4 rep 005a00507f000001 true 90 5 4 0 255 2 3 1 1 2 40

Request

struct Request
A decoded client request: version, command and target.
NameTypeDescription
versionint = 0VerSocks5 or VerSocks4.
cmdint = 0CmdConnect, CmdBind or CmdUDPAssociate.
addrAddressThe target.
userstr = ""The user id of a SOCKS4 request.

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

Reply

struct Reply
A decoded server reply: version, code and the address it reports.
NameTypeDescription
versionint = 0The version of the reply.
codeint = 0The reply code.
addrAddressThe address in the reply.

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

UDPHeader

struct UDPHeader
The header in front of every datagram that goes through a SOCKS5 UDP relay.
NameTypeDescription
fragint = 0The fragment number. Only 0 (no fragmentation) is supported.
addrAddressThe target or source of the datagram.
lengthint = 0How many bytes the header took.

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

ServerConfig

struct ServerConfig
The settings of a SOCKS server: port, authentication, which commands are allowed, how targets are reached and the time limits. Start from NewServerConfig.
NameTypeDescription
portint = 0The port to listen on; 0 picks a free one.
require_authbool = falseTrue when clients must log in. AddUser and SetAuth turn it on.
usersHashMapThe accepted user names and passwords.
authfunc(str, str): boolA custom check (user, password) -> bool used instead of users. Set it with SetAuth.
has_authbool = falseTrue when auth is in use.
allow_v4bool = trueTrue to accept SOCKS4 and SOCKS4a. The default is true. A server that requires passwords refuses SOCKS4 anyway.
allow_bindbool = falseTrue to allow the BIND command. The default is false, because BIND opens a listening port for the client.
allow_udpbool = trueTrue to allow UDP associate. The default is true.
bind_hoststr = "127.0.0.1"The address the server and the UDP and BIND sockets bind: 127.0.0.1 by default, ::1 or [::1] for IPv6 loopback, :: for IPv4 and IPv6, 0.0.0.0 for every IPv4 interface.
handshake_timeout_msint = 10000How long a client has to finish the handshake, in milliseconds. The default is 10000.
idle_timeout_msint = 0Cut a relayed connection after this many silent milliseconds. 0, the default, means never.
connectfunc(str, int): OutboundThe callback that reaches a target: (host, port) -> Outbound. The default is DirectConnect. Set it with SetConnector or SetDialer.
allowfunc(int, str, int): boolAn access rule (command, host, port) -> bool asked before each request; false answers with "not allowed". The default allows everything.
policytunnel.DestPolicyWhich destinations may be reached. By default loopback, private, link local, metadata and other special addresses are refused. See DestPolicy.
has_connectbool = falseTrue when a custom connector is set. Then the policy checks names and literal addresses only; the connector must resolve safely.
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.
max_per_sourceint = 32The most handshakes one source address may have in progress at once. The default is 32; 0 turns the limit 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.
net-socks-Auth.salam
import net.socks
import net.tcp

func echo_conn(arg: i64):
    c := tcp.ConnFromFd(arg)
    until true:
        data := tcp.Read(c, 4096)
        if data.len() == 0:
            break
        end
        tcp.Write(c, data)
    end
    tcp.Close(c)
end

func echo_accept(arg: i64):
    srv := tcp.Server { fd = arg }
    until true:
        c := tcp.Accept(srv)
        if not tcp.ConnOk(c):
            break
        end
        socks.Go(echo_conn, c.fd as void*)
    end
end

func start_echo(): int:
    listener := tcp.Bind(0)
    socks.Go(echo_accept, listener.fd as void*)
    ret tcp.Port(listener)
end

func say(label: str, r: socks.ConnectResult):
    println label + ": ok=" + r.ok + " code=" + r.code + " " + r.err
end

func main:
    echo := start_echo()

    mut config := socks.NewServerConfig(0)
    socks.AllowPrivate(config, true)
    socks.AddUser(config, "alice", "s3cret")
    println config.require_auth
    server := socks.Start(config)

    say("right password", socks.Connect(socks.NewClientAuth("127.0.0.1", server.port, "alice", "s3cret"), "127.0.0.1", echo))
    say("wrong password", socks.Connect(socks.NewClientAuth("127.0.0.1", server.port, "alice", "nope"), "127.0.0.1", echo))
    say("no credentials", socks.Connect(socks.NewClient("127.0.0.1", server.port), "127.0.0.1", echo))
    socks.Stop(server)

    mut custom := socks.NewServerConfig(0)
    socks.AllowPrivate(custom, true)
    socks.SetAuth(custom, (user: str, password: str): ret user == "bob" and password == "pw" end)
    println custom.has_auth
    second := socks.Start(custom)
    say("callback accepts", socks.Connect(socks.NewClientAuth("127.0.0.1", second.port, "bob", "pw"), "127.0.0.1", echo))
    say("callback refuses", socks.Connect(socks.NewClientAuth("127.0.0.1", second.port, "bob", "xx"), "127.0.0.1", echo))
    socks.Stop(second)
end
Outputtrue right password: ok=true code=0 wrong password: ok=false code=2 authentication failed no credentials: ok=false code=2 no acceptable auth method true callback accepts: ok=true code=0 callback refuses: ok=false code=2 authentication failed

Server

struct Server
A running SOCKS server. Start returns 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.
listenertcp.ServerThe underlying TCP listener.
stop_celli64*The flag that Stop clears to end the accept loop.

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

Socks5Inbound

struct Socks5Inbound
A SOCKS5 server for tunnel.InboundProtocol: it reads the greeting, authenticates and reads the request from a stream, so it can sit in a tunnel.MixedProtocol or behind tunnel.ListenTcp. Make one with NewInbound.
NameTypeDescription
configServerConfigThe server settings: users, allowed commands and limits.
net-socks-Inbound.salam
import net.socks
import net.tcp
import net.tunnel

func echo_conn(arg: i64):
    c := tcp.ConnFromFd(arg)
    until true:
        data := tcp.Read(c, 4096)
        if data.len() == 0:
            break
        end
        tcp.Write(c, data)
    end
    tcp.Close(c)
end

func echo_accept(arg: i64):
    srv := tcp.Server { fd = arg }
    until true:
        c := tcp.Accept(srv)
        if not tcp.ConnOk(c):
            break
        end
        socks.Go(echo_conn, c.fd as void*)
    end
end

func start_echo(): int:
    listener := tcp.Bind(0)
    socks.Go(echo_accept, listener.fd as void*)
    ret tcp.Port(listener)
end

func main:
    echo := start_echo()
    mut config := socks.NewServerConfig(0)
    socks.AddUser(config, "u", "p")
    mixed_config := socks.NewServerConfig(0)

    mut mixed := tunnel.NewMixed()
    tunnel.MixedSetSocks5(mixed, socks.NewInbound(config))
    tunnel.MixedSetSocks4(mixed, socks.NewInbound4(mixed_config))
    println socks.NewInbound(config).Name(), socks.NewInbound4(mixed_config).Name()

    forward := tunnel.NewForwarder(tunnel.Direct(), mixed)
    inbound := tunnel.ListenTcp(0)
    tunnel.ServeBackground(inbound, mixed, forward)

    five := socks.Connect(socks.NewClientAuth("127.0.0.1", inbound.port, "u", "p"), "127.0.0.1", echo)
    five.stream.Write("five")
    println five.ok, five.stream.Read(100)
    five.stream.Close()
    println "wrong password:", socks.Connect(socks.NewClientAuth("127.0.0.1", inbound.port, "u", "x"), "127.0.0.1", echo).ok

    mut four := socks.NewClient("127.0.0.1", inbound.port)
    four.version = socks.Socks4
    println "socks4 with auth required:", socks.Connect(four, "127.0.0.1", echo).code
    inbound.Stop()

    listener := tcp.Bind(0)
    port := tcp.Port(listener)
    socks.Go(serve_once, listener.fd as void*)
    direct := socks.Connect(socks.NewClient("127.0.0.1", port), "127.0.0.1", echo)
    direct.stream.Write("handled by hand")
    println direct.stream.Read(100)
    direct.stream.Close()
end

func serve_once(arg: i64):
    listener := tcp.Server { fd = arg }
    c := tcp.Accept(listener)
    socks.Handle(socks.NewServerConfig(0), tunnel.WrapTcp(c))
end
Outputsocks5 socks4 true five wrong password: false socks4 with auth required: 90 handled by hand

Name

func Name(): str
Returns socks5.

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

Handshake

func Handshake(s: dyn tunnel.Stream): tunnel.Session
Runs the SOCKS5 greeting, the optional user and password step and the request. Returns a tunnel.Session with the target, or a failed session after sending the right error reply.

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

Reply

func Reply(s: dyn tunnel.Stream, sess: tunnel.Session, ok: bool)
Sends the final SOCKS5 reply: succeeded, or host unreachable.

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

Socks4Inbound

struct Socks4Inbound
A SOCKS4 and SOCKS4a server for tunnel.InboundProtocol. Make one with NewInbound4.
NameTypeDescription
configServerConfigThe server settings.

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

Name

func Name(): str
Returns socks4.

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

Handshake

func Handshake(s: dyn tunnel.Stream): tunnel.Session
Reads a SOCKS4 or SOCKS4a request. SOCKS4 has no passwords, so a server that requires them refuses it.

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

Reply

func Reply(s: dyn tunnel.Stream, _sess: tunnel.Session, ok: bool)
Sends the SOCKS4 reply: granted (90) or rejected (91).

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

SocksConnector

struct SocksConnector
One SOCKS hop of a tunnel.Chain: it connects to a SOCKS server and asks it to connect onward. Make one with NewConnector.
NameTypeDescription
clientClientThe client settings: server address, version and credentials.

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

Server

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

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

Handshake

func Handshake(s: dyn tunnel.Stream, target: tunnel.Addr): bool
Runs the SOCKS handshake over the open stream for a target. True when the server agreed.

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

Name

func Name(): str
Returns socks5 or socks4.

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

Constants and variables

Client versions Socks…

The values of version.
NameValue
Socks55
Socks44
Socks4a40
net-socks-Socks4.salam
import net.socks
import net.tcp

func echo_conn(arg: i64):
    c := tcp.ConnFromFd(arg)
    until true:
        data := tcp.Read(c, 4096)
        if data.len() == 0:
            break
        end
        tcp.Write(c, data)
    end
    tcp.Close(c)
end

func echo_accept(arg: i64):
    srv := tcp.Server { fd = arg }
    until true:
        c := tcp.Accept(srv)
        if not tcp.ConnOk(c):
            break
        end
        socks.Go(echo_conn, c.fd as void*)
    end
end

func start_echo(): int:
    listener := tcp.Bind(0)
    socks.Go(echo_accept, listener.fd as void*)
    ret tcp.Port(listener)
end

func say(label: str, r: socks.ConnectResult):
    println label + ": ok=" + r.ok + " code=" + r.code + " " + r.err
end

func main:
    echo := start_echo()
    mut server_cfg := socks.NewServerConfig(0)
    socks.AllowPrivate(server_cfg, true)
    server := socks.Start(server_cfg)

    mut client := socks.NewClient("127.0.0.1", server.port)
    client.version = socks.Socks4
    r := socks.Connect(client, "127.0.0.1", echo)
    say("socks4 by address", r)
    r.stream.Write("four")
    println r.stream.Read(100)
    r.stream.Close()

    client.version = socks.Socks4a
    client.username = "tester"
    named := socks.Connect(client, "localhost", echo)
    say("socks4a by name", named)
    named.stream.Close()
    say("socks4 refused", socks.Connect(client, "127.0.0.1", tcp.FreePort()))
    socks.Stop(server)

    mut config := socks.NewServerConfig(0)
    socks.AllowPrivate(config, true)
    config.allow_v4 = false
    off := socks.Start(config)
    mut four := socks.NewClient("127.0.0.1", off.port)
    four.version = socks.Socks4
    println "socks4 switched off:", socks.Connect(four, "127.0.0.1", echo).ok
    socks.Stop(off)
end
Outputsocks4 by address: ok=true code=90 four socks4a by name: ok=true code=90 socks4 refused: ok=false code=91 request rejected or failed socks4 switched off: false

Version bytes Ver…

The first byte of a request or reply on the wire.
NameValue
VerSocks55
VerSocks44

Authentication methods Auth…

The methods offered in the SOCKS5 greeting.
NameValue
AuthNone0
AuthUserPass2
AuthNoAcceptable255

Commands Cmd…

The commands of cmd.
NameValue
CmdConnect1
CmdBind2
CmdUDPAssociate3

Address types Atyp…

The values of kind.
NameValue
AtypIPv41
AtypDomain3
AtypIPv64

SOCKS5 reply codes Rep…

The codes of a SOCKS5 reply, as code returns them.
NameValue
RepSucceeded0
RepGeneralFailure1
RepNotAllowed2
RepNetUnreachable3
RepHostUnreachable4
RepConnRefused5
RepTTLExpired6
RepCmdNotSupported7
RepAtypNotSupported8

SOCKS4 reply codes Rep4…

The codes of a SOCKS4 reply.
NameValue
Rep4Granted90
Rep4Rejected91
Rep4NoIdentd92
Rep4BadUser93

Functions

NewClient

func NewClient(host: str, port: int): Client
Makes a SOCKS5 client for a proxy, with no credentials.

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

NewClientAuth

func NewClientAuth(host: str, port: int, username: str, password: str): Client
Makes a SOCKS5 client that logs in with a user name and password.

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

ClientHandshake

func ClientHandshake(s: dyn tunnel.Stream, c: Client, cmd: int, host: str, port: int): HandshakeResult
Runs one SOCKS command (connect, bind or UDP associate) on a stream that is already open to a proxy. This is how a second proxy hop is added on top of the first.

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

Connect

func Connect(c: Client, host: str, port: int): ConnectResult
Connects to the proxy, runs the handshake for a target and returns the stream. The host may be a name (the proxy resolves it) or an IP.

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

DialConn

func DialConn(c: Client, host: str, port: int): tcp.Conn
Like Connect but returns a plain TCP connection for the net.tcp functions. An empty connection when it failed.

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

Bind

func Bind(c: Client, host: str, port: int): ConnectResult
Asks the proxy to wait for one incoming connection (SOCKS BIND), as FTP active mode needs. The first reply tells where it listens; call BindAccept for the second.
net-socks-Bind.salam
import net.socks
import net.tcp

func main:
    mut config := socks.NewServerConfig(0)
    socks.AllowPrivate(config, true)
    config.allow_bind = true
    server := socks.Start(config)
    client := socks.NewClient("127.0.0.1", server.port)

    first := socks.Bind(client, "127.0.0.1", 0)
    println "bind accepted:", first.ok, first.code, first.bound.port > 0
    peer := tcp.Dial("127.0.0.1", first.bound.port)
    second := socks.BindAccept(first.stream, client)
    println "peer connected:", second.ok
    tcp.Write(peer, "from the inbound peer")
    println first.stream.Read(100)
    first.stream.Write("reply to the peer")
    println tcp.Read(peer, 100)
    tcp.Close(peer)
    first.stream.Close()
    socks.Stop(server)

    mut plain_cfg := socks.NewServerConfig(0)
    socks.AllowPrivate(plain_cfg, true)
    plain := socks.Start(plain_cfg)
    off := socks.Bind(socks.NewClient("127.0.0.1", plain.port), "127.0.0.1", 0)
    println "bind not enabled:", off.ok, off.code, off.err
    socks.Stop(plain)
end
Outputbind accepted: true 0 true peer connected: true from the inbound peer reply to the peer bind not enabled: false 7 command not supported

BindAccept

func BindAccept(s: dyn tunnel.Stream, c: Client): HandshakeResult
Waits for the second BIND reply, which arrives when a peer connects to the bound port.

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

Associate

func Associate(c: Client): UDPSession
Opens a SOCKS5 UDP association. SOCKS4 has no UDP, so it fails for that version.

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

UDPSendBytes

func UDPSendBytes(u: UDPSession, host: str, port: int, ptr: void*, n: int): int
Sends a datagram from memory to a target through the relay. Returns the number of payload bytes sent.

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

UDPSend

func UDPSend(u: UDPSession, host: str, port: int, data: str): int
Sends a text datagram to a target through the relay.

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

UDPReceiveBytes

func UDPReceiveBytes(u: UDPSession, buf: void*, max: int, from &: Address): int
Receives one datagram into a buffer and fills in who it came from. Returns the payload size, or 0 or less on timeout or error.

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

UDPSetTimeout

func UDPSetTimeout(u: UDPSession, millis: int)
Sets how long a receive waits, in milliseconds.

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

CloseUDP

func CloseUDP(u: UDPSession)
Closes the UDP socket and the control connection.

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

Go

func Go(fn: i64, arg: void*): bool
Starts a function on a new thread. The function takes one integer argument, usually a pointer or a file descriptor.

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

Relay

func Relay(a: dyn tunnel.Stream, b: dyn tunnel.Stream, idle_ms: int): RelayStats
Copies data both ways between two streams until one ends. With an idle time in milliseconds, a silent connection is cut; 0 means no limit. Both streams are shut down at the end.

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

DirectConnect

func DirectConnect(host: str, port: int, timeout_ms: int): Outbound
The default connect callback: opens a TCP connection to a host and port with a timeout and wraps it as an Outbound. An unresolvable name gives host unreachable, a refusal gives connection refused.

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

PolicyConnect

func PolicyConnect(p: tunnel.DestPolicy, host: str, port: int, timeout_ms: int): Outbound
Connects to a host and port under a destination policy: names are resolved once, every answer is checked and the checked address is dialed. A refused target gives code 2.

ParseIPv4

func ParseIPv4(s: str, out &: Vector<u8>): bool
Parses IPv4 text into four bytes appended to a vector. False when it is not an address.

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

ParseIPv6

func ParseIPv6(s: str, out &: Vector<u8>): bool
Parses IPv6 text into sixteen bytes appended to a vector. False when it is not an address.

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

FormatIPv4

func FormatIPv4(v: Vector<u8>, off: int): str
Formats four bytes starting at an offset as dotted text.

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

FormatIPv6

func FormatIPv6(v: Vector<u8>, off: int): str
Formats sixteen bytes starting at an offset as IPv6 text.

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

ClassifyHost

func ClassifyHost(host: str): int
Returns AtypIPv4 for an IPv4 text, AtypIPv6 for an IPv6 text and AtypDomain for anything else.

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

NewAddress

func NewAddress(host: str, port: int): Address
Makes an Address, choosing the type from the host text.

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

EncodeAddress

func EncodeAddress(a: Address): Vector<u8>
Writes an address in SOCKS5 form: type, host, port. Empty when the address is bad.

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

DecodeAddress

func DecodeAddress(data: Vector<u8>, off: int, out &: Address): int
Reads an address from a vector at an offset. Returns how many bytes it took, 0 when more data is needed and -1 when it is invalid.

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

EncodeGreeting

func EncodeGreeting(methods: Vector<u8>): Vector<u8>
Writes the client's first message: the version and the list of authentication methods it offers.

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

EncodeMethodChoice

func EncodeMethodChoice(method: int): Vector<u8>
Writes the server's answer to the greeting: the method it picked.

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

EncodeAuthRequest

func EncodeAuthRequest(user: str, pass: str): Vector<u8>
Writes the user name and password message of SOCKS5 login.

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

EncodeAuthReply

func EncodeAuthReply(ok: bool): Vector<u8>
Writes the server's login answer: success or failure.

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

EncodeRequest

func EncodeRequest(cmd: int, a: Address): Vector<u8>
Writes a SOCKS5 request for a command and an address.

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

EncodeReply

func EncodeReply(code: int, a: Address): Vector<u8>
Writes a SOCKS5 reply with a code and an address.

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

DecodeRequest

func DecodeRequest(data: Vector<u8>, out &: Request): int
Reads a SOCKS5 request. Returns the bytes used, 0 for incomplete and -1 for invalid.

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

DecodeReply

func DecodeReply(data: Vector<u8>, out &: Reply): int
Reads a SOCKS5 reply. Returns the bytes used, 0 for incomplete and -1 for invalid.

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

EncodeUDPHeader

func EncodeUDPHeader(a: Address, frag: int): Vector<u8>
Writes the UDP relay header for an address and a fragment number.

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

DecodeUDPHeader

func DecodeUDPHeader(data: Vector<u8>, out &: UDPHeader): bool
Reads a UDP relay header. False when it is short or malformed.

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

Encode4Request

func Encode4Request(cmd: int, host: str, port: int, user: str): Vector<u8>
Writes a SOCKS4 or SOCKS4a request. A host name makes it SOCKS4a. Empty when the arguments are out of range.

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

Decode4Request

func Decode4Request(data: Vector<u8>, out &: Request): int
Reads a SOCKS4 or SOCKS4a request. Returns the bytes used, 0 for incomplete and -1 for invalid.

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

Encode4Reply

func Encode4Reply(code: int, host: str, port: int): Vector<u8>
Writes the eight byte SOCKS4 reply.

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

Decode4Reply

func Decode4Reply(data: Vector<u8>, out &: Reply): bool
Reads the eight byte SOCKS4 reply. False when it is short or its version byte is not 0.

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

ReplyText

func ReplyText(code: int): str
Returns a readable text for a reply code, such as connection refused.

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

ToHex

func ToHex(v: Vector<u8>): str
Returns the bytes of a vector as lowercase hex, handy for tests and logs.

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

NewServerConfig

func NewServerConfig(port: int): ServerConfig
Returns a config for a port with the defaults above.

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

AddUser

func AddUser(cfg &: ServerConfig, user: str, pass: str)
Adds a user and password and turns authentication on.

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

SetAuth

func SetAuth(cfg &: ServerConfig, check: func(str, str): bool)
Sets a custom login check and turns authentication on.

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

SetConnector

func SetConnector(cfg &: ServerConfig, connect: func(str, int): Outbound)
Sets the callback that reaches targets. Use it to route by name, as the example does.

The example for this item is shown with the ones above: net-socks-Policy.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.

Handle

func Handle(cfg: ServerConfig, s: dyn tunnel.Stream)
Serves one client on an open stream: reads the version byte, then runs SOCKS5 or SOCKS4. Use it to put a SOCKS server on a stream you accepted yourself.

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

Start

func Start(cfg: ServerConfig): Server
Binds the port and serves clients on background threads. Check ok.

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

Stop

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

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

Serve

func Serve(cfg: ServerConfig): bool
Starts a server and then never returns, for a program whose only job is to be the proxy. False when the port could not be bound.

NewConnector

func NewConnector(c: Client): SocksConnector
Makes a SocksConnector from a client. Use it with tunnel.Via or tunnel.ChainAdd.

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

NewInbound

func NewInbound(cfg: ServerConfig): Socks5Inbound
Makes a SOCKS5 inbound from a server config.

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

NewInbound4

func NewInbound4(cfg: ServerConfig): Socks4Inbound
Makes a SOCKS4 inbound from a server config.

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

SetDialer

func SetDialer(cfg &: ServerConfig, d: dyn tunnel.Dialer)
Makes the server reach targets through a tunnel.Dialer, so it can use rules, chains or a block list. It replaces the connect callback.

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