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:
NewClientorNewClientAuthfills aClient, andConnectreturns aConnectResultwith a stream. Failure is a value too:okis false andcodeanderrsay why. - SOCKS5 is the default. Set
versiontoSocks4orSocks4afor 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
ServerConfiggiven toStart. How it reaches targets is a callback (SetConnector) or atunnel.Dialer(SetDialer), which is how it is joined to routing rules and proxy chains. Associateopens a UDP relay andBindwaits 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 is127.0.0.1by default. Set it to0.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
tlswhen 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_v4can switch SOCKS4 off. allow_bindis 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 tobind_hosttoo, 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
::1or 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
streamis 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.
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)
endDialConn 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.
Startruns the accept loop on its own thread; connections run on further threads.Stopstops 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 ClientNewClient or NewClientAuth.| Name | Type | Description |
|---|---|---|
version | int = 5 | Socks5, Socks4 or Socks4a. The default is SOCKS5. |
host | str = "" | The proxy server's host name or IP. |
port | int = 0 | The proxy server's port. |
username | str = "" | The user name for SOCKS5 (or the user id field of SOCKS4). |
password | str = "" | The password for SOCKS5. Sent only when a user name is set. |
timeout_ms | int = 10000 | How long connecting and the handshake may take, in milliseconds. The default is 10000. |
tls | bool = false | True to wrap the connection to the proxy in TLS first (SOCKS over TLS). The default is false. |
server_name | str = "" | The name to send and check in the proxy's certificate when tls is on. Empty means the host. |
insecure | bool = false | True to skip the certificate check. For tests only. |
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)
endHandshakeResult
struct HandshakeResult| Name | Type | Description |
|---|---|---|
ok | bool = false | True when the server accepted the request. |
code | int = 0 | The reply code, see RepSucceeded and the Rep4Granted family. |
err | str = "" | A readable reason when it failed. |
bound | Address | The address the server reports: the one it bound (for BIND) or the one it connected from. |
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)
endConnectResult
struct ConnectResultConnect or Bind: the stream when it worked, and the reason when it did not.| Name | Type | Description |
|---|---|---|
ok | bool = false | True when the proxy connected you to the target. |
code | int = 0 | The reply code. |
err | str = "" | A readable reason when it failed. |
stream | dyn tunnel.Stream | The open stream to the target (through the proxy). Valid when ok is true. |
bound | Address | The address the proxy reports. |
The example for this item is shown with the ones above: net-socks-Connect.salam
UDPSession
struct UDPSessionAssociate.| Name | Type | Description |
|---|---|---|
ok | bool = false | True when the association was made. |
err | str = "" | A readable reason when it was not. |
control | dyn tunnel.Stream | The TCP connection that keeps the association alive. Closing it ends the association. |
sock | udp.Socket | The UDP socket used to send and receive datagrams. |
relay_host | str = "" | The relay address the proxy gave. |
relay_port | int = 0 | The relay port the proxy gave. |
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)
endOutbound
struct Outbound| Name | Type | Description |
|---|---|---|
ok | bool = false | True when the target was reached. |
code | int = 1 | The SOCKS reply code to give the client. The default is 1, general failure. |
err | str = "" | A readable reason, for logs. |
stream | dyn tunnel.Stream | The stream to the target when ok is true. |
bound | Address | The local address of the outgoing connection, reported back to the client. |
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)
endRelayStats
struct RelayStatsRelay moved in each direction.| Name | Type | Description |
|---|---|---|
a_to_b | i64 = 0 | Bytes from the first stream to the second. |
b_to_a | i64 = 0 | Bytes from the second stream to the first. |
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)
endAddress
struct Address| Name | Type | Description |
|---|---|---|
kind | int = 0 | AtypIPv4, AtypDomain or AtypIPv6. |
host | str = "" | The host name or IP text. |
port | int = 0 | The port. |
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
endRequest
struct Request| Name | Type | Description |
|---|---|---|
version | int = 0 | VerSocks5 or VerSocks4. |
cmd | int = 0 | CmdConnect, CmdBind or CmdUDPAssociate. |
addr | Address | The target. |
user | str = "" | 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| Name | Type | Description |
|---|---|---|
version | int = 0 | The version of the reply. |
code | int = 0 | The reply code. |
addr | Address | The address in the reply. |
The example for this item is shown with the ones above: net-socks-Codec.salam
UDPHeader
struct UDPHeader| Name | Type | Description |
|---|---|---|
frag | int = 0 | The fragment number. Only 0 (no fragmentation) is supported. |
addr | Address | The target or source of the datagram. |
length | int = 0 | How many bytes the header took. |
The example for this item is shown with the ones above: net-socks-Codec.salam
ServerConfig
struct ServerConfigNewServerConfig.| Name | Type | Description |
|---|---|---|
port | int = 0 | The port to listen on; 0 picks a free one. |
require_auth | bool = false | True when clients must log in. AddUser and SetAuth turn it on. |
users | HashMap | The accepted user names and passwords. |
auth | func(str, str): bool | A custom check (user, password) -> bool used instead of users. Set it with SetAuth. |
has_auth | bool = false | True when auth is in use. |
allow_v4 | bool = true | True to accept SOCKS4 and SOCKS4a. The default is true. A server that requires passwords refuses SOCKS4 anyway. |
allow_bind | bool = false | True to allow the BIND command. The default is false, because BIND opens a listening port for the client. |
allow_udp | bool = true | True to allow UDP associate. The default is true. |
bind_host | str = "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_ms | int = 10000 | How long a client has to finish the handshake, in milliseconds. The default is 10000. |
idle_timeout_ms | int = 0 | Cut a relayed connection after this many silent milliseconds. 0, the default, means never. |
connect | func(str, int): Outbound | The callback that reaches a target: (host, port) -> . The default is DirectConnect. Set it with SetConnector or SetDialer. |
allow | func(int, str, int): bool | An access rule (command, host, port) -> bool asked before each request; false answers with "not allowed". The default allows everything. |
policy | tunnel.DestPolicy | Which destinations may be reached. By default loopback, private, link local, metadata and other special addresses are refused. See DestPolicy. |
has_connect | bool = false | True when a custom connector is set. Then the policy checks names and literal addresses only; the connector must resolve safely. |
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. |
max_per_source | int = 32 | The most handshakes one source address may have in progress at once. The default is 32; 0 turns the limit 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. |
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)
endServer
struct ServerStart returns 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. |
listener | tcp.Server | The underlying TCP listener. |
stop_cell | i64* | 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 Socks5Inboundtunnel.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.| Name | Type | Description |
|---|---|---|
config | ServerConfig | The server settings: users, allowed commands and limits. |
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))
endName
func Name(): strsocks5.The example for this item is shown with the ones above: net-socks-Inbound.salam
Handshake
func Handshake(s: dyn tunnel.Stream): tunnel.Sessiontunnel.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)The example for this item is shown with the ones above: net-socks-Inbound.salam
Socks4Inbound
struct Socks4Inboundtunnel.InboundProtocol. Make one with NewInbound4.| Name | Type | Description |
|---|---|---|
config | ServerConfig | The server settings. |
The example for this item is shown with the ones above: net-socks-Inbound.salam
Name
func Name(): strsocks4.The example for this item is shown with the ones above: net-socks-Inbound.salam
Handshake
func Handshake(s: dyn tunnel.Stream): tunnel.SessionThe 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)The example for this item is shown with the ones above: net-socks-Inbound.salam
SocksConnector
struct SocksConnectortunnel.Chain: it connects to a SOCKS server and asks it to connect onward. Make one with NewConnector.| Name | Type | Description |
|---|---|---|
client | Client | The 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.AddrThe example for this item is shown with the ones above: net-socks-Chain.salam
Handshake
func Handshake(s: dyn tunnel.Stream, target: tunnel.Addr): boolThe example for this item is shown with the ones above: net-socks-Chain.salam
Name
func Name(): strsocks5 or socks4.The example for this item is shown with the ones above: net-socks-Chain.salam
Constants and variables
Client versions Socks…
version.| Name | Value |
|---|---|
Socks5 | 5 |
Socks4 | 4 |
Socks4a | 40 |
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)
endVersion bytes Ver…
| Name | Value |
|---|---|
VerSocks5 | 5 |
VerSocks4 | 4 |
Authentication methods Auth…
| Name | Value |
|---|---|
AuthNone | 0 |
AuthUserPass | 2 |
AuthNoAcceptable | 255 |
Commands Cmd…
cmd.| Name | Value |
|---|---|
CmdConnect | 1 |
CmdBind | 2 |
CmdUDPAssociate | 3 |
Address types Atyp…
kind.| Name | Value |
|---|---|
AtypIPv4 | 1 |
AtypDomain | 3 |
AtypIPv6 | 4 |
SOCKS5 reply codes Rep…
code returns them.| Name | Value |
|---|---|
RepSucceeded | 0 |
RepGeneralFailure | 1 |
RepNotAllowed | 2 |
RepNetUnreachable | 3 |
RepHostUnreachable | 4 |
RepConnRefused | 5 |
RepTTLExpired | 6 |
RepCmdNotSupported | 7 |
RepAtypNotSupported | 8 |
SOCKS4 reply codes Rep4…
| Name | Value |
|---|---|
Rep4Granted | 90 |
Rep4Rejected | 91 |
Rep4NoIdentd | 92 |
Rep4BadUser | 93 |
Functions
NewClient
func NewClient(host: str, port: int): ClientThe 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): ClientThe 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): HandshakeResultThe example for this item is shown with the ones above: net-socks-Chain.salam
Connect
func Connect(c: Client, host: str, port: int): ConnectResultThe example for this item is shown with the ones above: net-socks-Connect.salam
DialConn
func DialConn(c: Client, host: str, port: int): tcp.ConnConnect 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): ConnectResultBindAccept for the second.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)
endBindAccept
func BindAccept(s: dyn tunnel.Stream, c: Client): HandshakeResultThe example for this item is shown with the ones above: net-socks-Bind.salam
Associate
func Associate(c: Client): UDPSessionThe 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): intThe 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): intThe 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): intThe example for this item is shown with the ones above: net-socks-Udp.salam
UDPSetTimeout
func UDPSetTimeout(u: UDPSession, millis: int)The example for this item is shown with the ones above: net-socks-Udp.salam
CloseUDP
func CloseUDP(u: UDPSession)The example for this item is shown with the ones above: net-socks-Udp.salam
Go
func Go(fn: i64, arg: void*): boolThe 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): RelayStatsThe example for this item is shown with the ones above: net-socks-Relay.salam
DirectConnect
func DirectConnect(host: str, port: int, timeout_ms: int): OutboundOutbound. 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): OutboundParseIPv4
func ParseIPv4(s: str, out &: Vector<u8>): boolThe example for this item is shown with the ones above: net-socks-Codec.salam
ParseIPv6
func ParseIPv6(s: str, out &: Vector<u8>): boolThe example for this item is shown with the ones above: net-socks-Codec.salam
FormatIPv4
func FormatIPv4(v: Vector<u8>, off: int): strThe example for this item is shown with the ones above: net-socks-Codec.salam
FormatIPv6
func FormatIPv6(v: Vector<u8>, off: int): strThe example for this item is shown with the ones above: net-socks-Codec.salam
ClassifyHost
func ClassifyHost(host: str): intAtypIPv4 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): AddressAddress, 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>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): intThe example for this item is shown with the ones above: net-socks-Codec.salam
EncodeGreeting
func EncodeGreeting(methods: Vector<u8>): Vector<u8>The example for this item is shown with the ones above: net-socks-Codec.salam
EncodeMethodChoice
func EncodeMethodChoice(method: int): Vector<u8>The example for this item is shown with the ones above: net-socks-Codec.salam
EncodeAuthRequest
func EncodeAuthRequest(user: str, pass: str): Vector<u8>The example for this item is shown with the ones above: net-socks-Codec.salam
EncodeAuthReply
func EncodeAuthReply(ok: bool): Vector<u8>The example for this item is shown with the ones above: net-socks-Codec.salam
EncodeRequest
func EncodeRequest(cmd: int, a: Address): Vector<u8>The example for this item is shown with the ones above: net-socks-Codec.salam
EncodeReply
func EncodeReply(code: int, a: Address): Vector<u8>The example for this item is shown with the ones above: net-socks-Codec.salam
DecodeRequest
func DecodeRequest(data: Vector<u8>, out &: Request): intThe example for this item is shown with the ones above: net-socks-Codec.salam
DecodeReply
func DecodeReply(data: Vector<u8>, out &: Reply): intThe example for this item is shown with the ones above: net-socks-Codec.salam
EncodeUDPHeader
func EncodeUDPHeader(a: Address, frag: int): Vector<u8>The example for this item is shown with the ones above: net-socks-Codec.salam
DecodeUDPHeader
func DecodeUDPHeader(data: Vector<u8>, out &: UDPHeader): boolThe 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>The example for this item is shown with the ones above: net-socks-Codec.salam
Decode4Request
func Decode4Request(data: Vector<u8>, out &: Request): intThe 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>The example for this item is shown with the ones above: net-socks-Codec.salam
Decode4Reply
func Decode4Reply(data: Vector<u8>, out &: Reply): boolThe example for this item is shown with the ones above: net-socks-Codec.salam
ReplyText
func ReplyText(code: int): strconnection refused.The example for this item is shown with the ones above: net-socks-Codec.salam
ToHex
func ToHex(v: Vector<u8>): strThe example for this item is shown with the ones above: net-socks-Codec.salam
NewServerConfig
func NewServerConfig(port: int): ServerConfigThe example for this item is shown with the ones above: net-socks-Connect.salam
AddUser
func AddUser(cfg &: ServerConfig, user: str, pass: str)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)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)The example for this item is shown with the ones above: net-socks-Policy.salam
SetPolicy
func SetPolicy(cfg &: ServerConfig, p: tunnel.DestPolicy)AllowPrivate
func AllowPrivate(cfg &: ServerConfig, enabled: bool)Handle
func Handle(cfg: ServerConfig, s: dyn tunnel.Stream)The example for this item is shown with the ones above: net-socks-Inbound.salam
Start
func Start(cfg: ServerConfig): Serverok.The example for this item is shown with the ones above: net-socks-Connect.salam
Stop
func Stop(sv: Server)The example for this item is shown with the ones above: net-socks-Connect.salam
Serve
func Serve(cfg: ServerConfig): boolNewConnector
func NewConnector(c: Client): SocksConnectorSocksConnector 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): Socks5InboundThe example for this item is shown with the ones above: net-socks-Inbound.salam
NewInbound4
func NewInbound4(cfg: ServerConfig): Socks4InboundThe example for this item is shown with the ones above: net-socks-Inbound.salam
SetDialer
func SetDialer(cfg &: ServerConfig, d: dyn tunnel.Dialer)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