net/proxy
Connecting through an HTTP or SOCKS5 proxy, alone or in a chain.
import net.proxy
A proxy is a middleman that makes the connection on behalf of the program. This package builds the tunnel and gives back an ordinary net/tcp connection, read and written exactly as a direct one is.
Two kinds of proxy are supported. An HTTP proxy (NewHTTPProxy, often on port 3128 or 8080) opens the tunnel with a CONNECT request; a SOCKS5 proxy (NewSOCKS5Proxy, port 1080, or 9050 for Tor) uses its own small binary handshake. Both take "host:port", and the ...Auth forms add a user name and password. Dial returns a tcp.Conn; when the proxy cannot be reached or refuses, the connection is empty, so always check it with tcp.ConnOk.
What trips people up
- The handshake has no timeout. A proxy that accepts the connection but never answers makes
Dialwait forever, and there is no way to set a deadline before the handshake. Run the dial in a thread you can give up on if that matters. - Nothing is read from the environment.
HTTPS_PROXY,ALL_PROXYandNO_PROXYare ignored; read them yourself, as the first guide does. - The address must be
host:portwith a name or an IPv4 address. The text is split at the first colon, so an IPv6 proxy address such as[::1]:1080is not understood, and a missing port becomes 0. - Names are resolved by the proxy. SOCKS5 sends the target as a host name, so the proxy does the DNS lookup. That is what you want with Tor, but it means the target must be reachable by name from the proxy's network.
- The result is a plain TCP connection. For HTTPS, upgrade it with
tcp.UpgradeTLSas the HTTPS guide shows; the package does not do TLS to the proxy itself.
Reading proxy settings from a URL
Proxy settings usually arrive as one URL, for example from an HTTPS_PROXY environment variable or a config file: socks5://user:pass@host:1080. The package takes the parts separately, so a program needs a few lines to split the scheme, the credentials and the address. Unknown schemes should be reported rather than silently ignored.
import net.proxy
import str
func from_url(url: str, ok &: bool): proxy.Proxy:
ok = true
mut rest := url
mut socks := false
if str.StartsWith(rest, "socks5://"):
socks = true
rest = rest.substr(9, rest.len() - 9)
else str.StartsWith(rest, "http://"):
rest = rest.substr(7, rest.len() - 7)
else:
ok = false
ret proxy.Proxy {}
end
mut user := ""
mut pass := ""
at := rest.find("@")
if at >= 0:
cred := rest.substr(0, at)
rest = rest.substr(at + 1, rest.len() - at - 1)
colon := cred.find(":")
user = cred.substr(0, colon)
pass = cred.substr(colon + 1, cred.len() - colon - 1)
end
if socks: ret proxy.NewSOCKS5ProxyAuth(rest, user, pass) end
ret proxy.NewHTTPProxyAuth(rest, user, pass)
end
func main:
urls := ["http://10.0.0.5:3128", "socks5://127.0.0.1:9050", "socks5://ali:s3cret@gw.example.net:1080", "ftp://old.example.net:21"]
each u in urls:
mut ok := false
p := from_url(u, ok)
if not ok:
println u, "-> not supported"
continue
end
mut kind := "http"
if p.kind == proxy.TypeSOCKS5: kind = "socks5" end
println u, "->", kind, p.host, p.port, "user=[" + p.username + "]"
end
endWhat the tunnel looks like
This program runs offline: it starts a tiny fake HTTP proxy on a free local port in a thread, dials through it and prints the request the proxy received. That request is the whole HTTP proxy protocol: a CONNECT host:port line, the Host header, and with credentials a Proxy-Authorization: Basic header holding user:password in Base64. Once the proxy answers 200, every byte is passed straight through, which is why the echo reply comes back on the same connection.
import net.proxy
import net.tcp
import str
mut listener := tcp.Server {}
mut seen := ""
func fake_proxy():
c := tcp.Accept(listener)
seen = tcp.Read(c, 4096)
tcp.Write(c, "HTTP/1.1 200 Connection established\r\n\r\n")
msg := tcp.Read(c, 100)
tcp.Write(c, "echo: " + msg)
tcp.Close(c)
end
func main:
listener = tcp.Bind(0)
worker := spawn(fake_proxy)
p := proxy.NewHTTPProxyAuth("127.0.0.1:" + str.FromInt(tcp.Port(listener) as i64), "ali", "secret")
conn := proxy.Dial(p, "example.com", 443)
if not tcp.ConnOk(conn):
println "the proxy refused the tunnel"
ret 1
end
tcp.Write(conn, "hello")
println tcp.Read(conn, 100)
tcp.Close(conn)
join(worker)
print str.Replace(seen, "\r\n", "\n")
tcp.CloseServer(listener)
ret 0
endBasic authentication is only Base64, not encryption. Over an untrusted network, use a proxy you reach through a VPN or SSH tunnel, or send only TLS traffic through it as in the next section.
HTTPS through a proxy
A tunnel carries any bytes, including TLS. Dial the proxy for port 443 of the real host, then turn the connection into TLS with tcp.UpgradeTLS and a tls config for that host name. The proxy sees only the host name and encrypted bytes. To try it, start a local proxy such as Squid:
docker run -d -p 3128:3128 ubuntu/squid
import net.proxy
import net.tcp
import tls
import str
func first_line(s: str): str:
end_at := s.find("\r\n")
if end_at < 0: ret s end
ret s.substr(0, end_at)
end
func main:
host := "example.com"
p := proxy.NewHTTPProxy("127.0.0.1:3128")
conn := proxy.Dial(p, host, 443)
if not tcp.ConnOk(conn):
println "no tunnel through " + p.host + ":" + str.FromInt(p.port as i64)
ret 1
end
mut secure := tcp.UpgradeTLS(conn, tls.NewConfig(host))
if not tls.ConnOk(secure):
println "TLS failed: " + tls.ConnError(secure)
ret 1
end
tls.Write(secure, "GET / HTTP/1.1\r\nHost: " + host + "\r\nConnection: close\r\n\r\n")
println first_line(tls.Read(secure, 4096))
tls.Close(secure)
ret 0
endWith no proxy listening, the program prints no tunnel through 127.0.0.1:3128 and exits with 1.
A chain with a direct fallback
A Chain connects through several proxies in order: the first proxy opens a tunnel to the second, and so on, and the last one reaches the target. Build it with NewChain and Add, then call DialChain. If any hop fails the result is empty, and a program that may run both inside and outside a corporate network can then try tcp.Dial directly.
import net.proxy
import net.tcp
func main:
mut chain := proxy.NewChain()
proxy.Add(chain, proxy.NewSOCKS5Proxy("127.0.0.1:9050"))
proxy.Add(chain, proxy.NewHTTPProxyAuth("exit.example.net:3128", "team", "s3cret"))
mut conn := proxy.DialChain(chain, "example.com", 80)
if not tcp.ConnOk(conn):
println "the chain is broken, trying direct"
conn = tcp.Dial("example.com", 80)
end
if not tcp.ConnOk(conn):
println "example.com is unreachable"
ret 1
end
tcp.Write(conn, "HEAD / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n")
reply := tcp.Read(conn, 4096)
println reply.substr(0, reply.find("\r\n"))
tcp.Close(conn)
ret 0
endEach hop adds a round trip before the first byte of real data, so keep chains short. To use Tor, run it locally (docker run -d -p 9050:9050 dperson/torproxy) and add NewSOCKS5Proxy("127.0.0.1:9050") as the first hop.
Types
Proxy
struct Proxy| Name | Type | Description |
|---|---|---|
kind | int = 0 | The kind; one of the Type constants. |
host | str = "" | The host. |
port | int = 0 | The port. |
username | str = "" | The user name, when one is needed. |
password | str = "" | The password. |
import net.proxy
func main:
a := proxy.NewHTTPProxy("10.0.0.1:3128")
println a.kind == proxy.TypeHTTP, a.host, a.port
b := proxy.NewHTTPProxyAuth("proxy.local:8080", "ali", "secret")
println b.username, b.password
c := proxy.NewSOCKS5Proxy("127.0.0.1:1080")
d := proxy.NewSOCKS5ProxyAuth("socks.local:1080", "u", "p")
println c.kind == proxy.TypeSOCKS5, d.port
mut chain := proxy.NewChain()
proxy.Add(chain, c)
proxy.Add(chain, a)
println chain.items.len()
endChain
struct Chain| Name | Type | Description |
|---|---|---|
items | Vector | The proxies in order. |
The example for this item is shown with the ones above: net-proxy-Proxy.salam
Constants and variables
TypeHTTP
const TypeHTTP: i32 = 0The example for this item is shown with the ones above: net-proxy-Proxy.salam
TypeSOCKS5
const TypeSOCKS5: i32 = 1The example for this item is shown with the ones above: net-proxy-Proxy.salam
Functions
NewHTTPProxy
func NewHTTPProxy(addr: str): Proxyhost:port address; the text is split at the first colon, so IPv6 addresses do not work.The example for this item is shown with the ones above: net-proxy-Proxy.salam
NewHTTPProxyAuth
func NewHTTPProxyAuth(addr: str, username: str, password: str): ProxyThe example for this item is shown with the ones above: net-proxy-Proxy.salam
NewSOCKS5Proxy
func NewSOCKS5Proxy(addr: str): ProxyThe example for this item is shown with the ones above: net-proxy-Proxy.salam
NewSOCKS5ProxyAuth
func NewSOCKS5ProxyAuth(addr: str, username: str, password: str): ProxyThe example for this item is shown with the ones above: net-proxy-Proxy.salam
Dial
func Dial(p: Proxy, target_host: str, target_port: int): tcp.Conntcp.ConnOk) when the proxy cannot be reached or refuses. The handshake waits without a time limit.import net.proxy
import net.tcp
func main:
p := proxy.NewSOCKS5Proxy("127.0.0.1:1080")
conn := proxy.Dial(p, "example.com", 80)
mut chain := proxy.NewChain()
proxy.Add(chain, p)
conn2 := proxy.DialChain(chain, "example.com", 80)
println tcp.ConnOk(conn), tcp.ConnOk(conn2)
endNewChain
func NewChain(): ChainThe example for this item is shown with the ones above: net-proxy-Proxy.salam
Add
func Add(c &: Chain, p: Proxy)The example for this item is shown with the ones above: net-proxy-Proxy.salam
DialChain
func DialChain(c: Chain, target_host: str, target_port: int): tcp.ConnThe example for this item is shown with the ones above: net-proxy-Dial.salam