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/singproxy
Use 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" ) func main() { // 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) if err != 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, addr string) (net.Conn, error) { tcpAddr, err := net.ResolveTCPAddr(network, addr) if err != nil { return nil, err } // Use the proxy's DialContext method to establish the connection. return proxy.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") if err != nil { panic(fmt.Sprintf("Request failed through proxy: %v", err)) } defer resp.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. type TestResult struct { ProxyName string Latency time.Duration Success bool Error error } func main() { 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. if len(errs) > 0 { fmt.Printf("\nEncountered %d parsing errors:\n", len(errs)) for _, err := range errs { fmt.Printf(" - %v\n", err) } } fmt.Printf("\nSuccessfully parsed %d proxies. Starting connection tests...\n\n", len(proxies)) var wg sync.WaitGroup results := make(chan TestResult, len(proxies)) // Test each valid proxy in its own goroutine. for _, p := range proxies { wg.Add(1) go testProxy(p, results, &wg) } // Wait for all tests to complete. wg.Wait() close(results) // Print the collected results. for result := range results { if result.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. func testProxy(p singproxy.Proxy, results chan<- TestResult, wg *sync.WaitGroup) { defer wg.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, addr string) (net.Conn, error) { tcpAddr, err := net.ResolveTCPAddr(network, addr) if err != nil { return nil, err } return p.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") if err != nil { result.Success = false result.Error = err results <- result return } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { result.Success = true result.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 = false result.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.