Sing-Proxy is a high-level Go library designed to simplify connecting through various proxy protocols. Built as a
wrapper around the powerful sagernet/sing-box core, its primary feature is its exceptional ability to parse and handle
a vast number of non-standard, malformed, and "dirty" proxy URLs found in the wild.
If you need to consume proxy lists from public Telegram channels, subscription services, or other non-standard sources, Sing-Proxy is built to handle the mess for you.
- Unified
ProxyInterface: A single, simple API for Shadowsocks, VLESS, VMess, Trojan, and more. - Powered by
sing-box: Leverages the robust, performant, and up-to-date networking core ofsing-box. - Resilient URL Parsing: Intelligently cleans and fixes common errors in proxy URLs before parsing, dramatically increasing success rates with public proxy lists.
- Wide Protocol Support:
- VLESS (with REALITY, gRPC, WebSocket, HTTP Upgrade)
- VMess
- Trojan
- Shadowsocks (including
encryption=nonevariants and SIP003 formats) - Hysteria/Hysteria2
- WireGuard
- SOCKS5/SOCKS4
- HTTP/HTTPS/HTTP2
- TUIC
- SSH
- AnyTLS
- NaiveProxy
- ShadowTLS
- Concurrency-Ready: Includes a
FromURLshelper to parse large lists of proxies in parallel.
go get github.com/SyNdicateFoundation/singproxyUse following tags for building you binary
-tags=with_utls,with_gvisor,with_quic,with_dhcp,with_acme,with_clash_api,with_wireguard
The library exposes a straightforward API. You provide a proxy URL, and you get back an object that satisfies the
Proxy interface, which has a DialContext method you can use in any standard Go networking code.
package main
import (
"context""fmt""io""net""net/http""time""github.com/SyNdicateFoundation/singproxy"
)
funcmain() {
// Example VLESS URL with a REALITY configuration and a name in the fragment.proxyURL:="vless://a-uuid@example.com:443?security=reality&sni=sni.example.com&fp=chrome&pbk=YOUR_REALITY_KEY&sid=abcdef1234#MyVlessProxy"timeout:=8*time.Second// 1. Parse the URL using the factory function.proxy, err:=singproxy.FromURL(timeout, proxyURL)
iferr!=nil {
panic(fmt.Sprintf("Failed to parse proxy URL: %v", err))
}
// The String() method returns the cleaned name from the URL fragment.fmt.Printf("Successfully parsed proxy: %s\n", proxy.String()) // Prints "vless://a-uuid@example.com:443?security=reality&sni=sni.example.com&fp=chrome&pbk=YOUR_REALITY_KEY&sid=abcdef1234"// 2. Create an HTTP client that uses the proxy's custom dialer.httpClient:=&http.Client{
Timeout: 15*time.Second,
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addrstring) (net.Conn, error) {
tcpAddr, err:=net.ResolveTCPAddr(network, addr)
iferr!=nil {
returnnil, err
}
// Use the proxy's DialContext method to establish the connection.returnproxy.DialContext(ctx, network, tcpAddr)
},
},
}
// 3. Make a request to a test endpoint through the proxy.fmt.Println("Making request to httpbin.org/get...")
resp, err:=httpClient.Get("https://httpbin.org/get")
iferr!=nil {
panic(fmt.Sprintf("Request failed through proxy: %v", err))
}
deferresp.Body.Close()
body, _:=io.ReadAll(resp.Body)
fmt.Printf("Status: %s\n", resp.Status)
fmt.Printf("Response Body (first 100 bytes): %.100s...\n", string(body))
}This complete example demonstrates how to parse multiple URLs at once and test their connectivity concurrently.
package main
import (
"context""fmt""io""net""net/http""sync""time""github.com/SyNdicateFoundation/singproxy"
)
// TestResult holds the outcome of a single proxy test.typeTestResultstruct {
ProxyNamestringLatency time.DurationSuccessboolErrorerror
}
funcmain() {
proxyURLs:= []string{
"direct", // A direct connection for baseline comparison."vless://a-uuid@example.com:443?security=tls&sni=example.com#VLESS-Example",
"ss://YWVzLTI1Ni1nY206cGFzc3dvcmQ=@example.com:8080#Shadowsocks-Example",
"trojan://password@example.com:443?sni=example.com#Trojan-Example",
"this-is-an-invalid-url", // An invalid URL to show error handling."hysteria2://-->invalid-user@example.com:4567#Malformed-Hysteria2", // Will be cleaned and parsed.
}
fmt.Printf("Parsing %d proxy URLs...\n", len(proxyURLs))
timeout:=8*time.Second// Use FromURLs to parse all proxies concurrently. It returns valid proxies and any errors.proxies, errs:=singproxy.FromURLs(timeout, proxyURLs...)
// Print any parsing errors encountered.iflen(errs) >0 {
fmt.Printf("\nEncountered %d parsing errors:\n", len(errs))
for_, err:=rangeerrs {
fmt.Printf(" - %v\n", err)
}
}
fmt.Printf("\nSuccessfully parsed %d proxies. Starting connection tests...\n\n", len(proxies))
varwg sync.WaitGroupresults:=make(chanTestResult, len(proxies))
// Test each valid proxy in its own goroutine.for_, p:=rangeproxies {
wg.Add(1)
gotestProxy(p, results, &wg)
}
// Wait for all tests to complete.wg.Wait()
close(results)
// Print the collected results.forresult:=rangeresults {
ifresult.Success {
fmt.Printf("[SUCCESS] Proxy: %-30s | Latency: %s\n", result.ProxyName, result.Latency)
} else {
fmt.Printf("[FAILURE] Proxy: %-30s | Error: %v\n", result.ProxyName, result.Error)
}
}
}
// testProxy attempts to make an HTTP GET request through the given proxy and sends the result to a channel.functestProxy(p singproxy.Proxy, resultschan<-TestResult, wg*sync.WaitGroup) {
deferwg.Done()
result:=TestResult{ProxyName: p.String()}
startTime:=time.Now()
httpClient:=&http.Client{
Timeout: 10*time.Second, // Timeout for the entire HTTP request.Transport: &http.Transport{
// Use the proxy's custom dialer.DialContext: func(ctx context.Context, network, addrstring) (net.Conn, error) {
tcpAddr, err:=net.ResolveTCPAddr(network, addr)
iferr!=nil {
returnnil, err
}
returnp.DialContext(ctx, network, tcpAddr)
},
// Disable keep-alives for more accurate, isolated latency measurement.DisableKeepAlives: true,
},
}
// Make the test request.resp, err:=httpClient.Get("https://httpbin.org/get")
iferr!=nil {
result.Success=falseresult.Error=errresults<-resultreturn
}
deferresp.Body.Close()
ifresp.StatusCode==http.StatusOK {
result.Success=trueresult.Latency=time.Since(startTime)
// Drain the body to properly measure the time for the full response._, _=io.Copy(io.Discard, resp.Body)
} else {
result.Success=falseresult.Error=fmt.Errorf("bad status: %s", resp.Status)
}
results<-result
}Many proxy providers and aggregators generate URLs that don't strictly adhere to RFC standards. They often contain extra
metadata, comments, or invalid characters that cause standard Go parsers like net/url.Parse to fail.
Sing-Proxy addresses this with a multi-layered cleaning process in its FromURL function:
- Isolate Name: The URL fragment (
#...) is immediately separated to preserve the proxy's intended name. - General Cleaning: Removes common junk query parameters like
ps,remarks,tag, etc. - Protocol-Specific Cleaning: Before parsing, it applies a set of "brute-force" rules tailored to each protocol's
common mistakes:
- VMess: Strips all non-Base64 characters from the payload.
- Hysteria2/Trojan: Escapes invalid characters found in
userinfo(like-->,^,🤠). - Shadowsocks: Corrects malformed structures like
ss://user@host:port@comment. - VLESS: Cleans junk data appended to hostnames (e.g.,
...:port---Telegram---).
- Standard Parsing: Only after these cleaning steps is the URL passed to Go's standard parser.
- Post-Parsing Fallbacks: Within the individual protocol parsers, it applies fallbacks for common logical errors,
such as using a default cipher when a Shadowsocks method is missing or handling non-standard transport names like
xhttp.
The library supports the following URL schemes:
- VMess (
vless://) - HTTP / HTTPS / HTTP2 (
http://,https://,http2://) - VLESS (
vless://) - Trojan (
trojan://,trojan-go://) - Shadowsocks (
ss://) - TUIC (
tuic://) - Hysteria (
hysteria://) - Hysteria2 (
hysteria2://) - SSH (
ssh://) - SOCKS5 / SOCKS4 (
socks5://,socks4://) - WireGuard (
wireguard://) - Direct (
direct) - Tor (
tor://) - AnyTLS (
anytls://,atls://) - ShadowTLS (
shadowtls://) - NaiveProxy (
naive://,naive+https://)
This project is licensed under the MIT License.