Search code examples
goproxyrate-limitingsocks5

Golang socks5 proxy with traffic limit forwarding to the next socks5 proxy


The first socks5 proxy with traffic limit, the next socks5 proxy without any limit.

Use curl download something like:

curl -x socks5://127.0.0.1:10801 -O https://mirrors.xxxx.iso

At first, curl downloads fine, the traffic limit is working, but after while curl speed becomes 0.

package main

import (
    "fmt"
    "github.com/juju/ratelimit"
    "io"
    "net"
)

var nextProxy = "127.0.0.1:10802"

func main() {
    server, err := net.Listen("tcp", ":10801")
    if err != nil {
        fmt.Printf("Listen failed: %v\n", err)
        return
    }

    for {
        client, err := server.Accept()
        if err != nil {
            fmt.Printf("Accept failed: %v", err)
            continue
        }

        go handle(client)
    }
}

func handle(conn net.Conn) {

    // add close
    defer conn.Close()

    // next socks5 proxy
    nextConn, err := net.Dial("tcp", nextProxy)
    if err != nil {
        fmt.Println("Failed to connect to next proxy:", err)
        conn.Close()
        return
    }
    defer nextConn.Close()

    go func() {
        _, err := Copy(nextConn, conn)
        if err != nil {
            fmt.Println("Error copying data to next proxy:", err)
            conn.Close()
        }
    }()

    _, err = Copy(conn, nextConn)
    if err != nil {
        fmt.Println("Error copying data from next proxy:", err)
        conn.Close()
    }
}

func Copy(left io.Writer, right io.Reader) (int64, error) {
    
    right = ratelimit.Reader(right, ratelimit.NewBucketWithRate(102400, 102400))

    return io.Copy(left, right)
}

I've tried another Copy like:

func copyWithRateLimit(dst io.Writer, src io.Reader, limiter *ratelimit.Bucket) {
    // buffer
    buf := make([]byte, 32*1024) // 32KB buffer
    for {
        n, err := src.Read(buf)
        if err != nil {
            if err == io.EOF {
                return
            }
            fmt.Println("Error reading from source:", err)
            return
        }

        // Rate limit the data
        if limit := limiter.WaitMaxDuration(int64(n), time.Second); !limit {
            fmt.Println("Error rate limiting:", err)
            return
        }

        if _, err := dst.Write(buf[:n]); err != nil {
            fmt.Println("Error writing to destination:", err)
            return
        }
    }
}

Even if you increase the buffer to 64*1024, the speed still goes to 0 and eventually it doesn't work.


There is the next socks5 proxy without traffic limit like:

package main

import (
    "encoding/binary"
    "errors"
    "fmt"
    "io"
    "net"
)

func main() {
    server, err := net.Listen("tcp", ":10802")
    if err != nil {
        fmt.Printf("Listen failed: %v\n", err)
        return
    }

    for {
        client, err := server.Accept()
        if err != nil {
            fmt.Printf("Accept failed: %v", err)
            continue
        }

        go process(client)
    }
}

func process(client net.Conn) {
    if err := Socks5Auth(client); err != nil {
        fmt.Println("auth error:", err)
        client.Close()
        return
    }

    target, err := Socks5Connect(client)
    if err != nil {
        fmt.Println("connect error:", err)
        client.Close()
        return
    }

    Socks5Forward(client, target)
}

func Socks5Auth(client net.Conn) (err error) {
    buf := make([]byte, 256)

    n, err := io.ReadFull(client, buf[:2])
    if n != 2 {
        return errors.New("reading header: " + err.Error())
    }

    ver, nMethods := int(buf[0]), int(buf[1])
    if ver != 5 {
        return errors.New("invalid version")
    }

    n, err = io.ReadFull(client, buf[:nMethods])
    if n != nMethods {
        return errors.New("reading methods: " + err.Error())
    }

    n, err = client.Write([]byte{0x05, 0x00})
    if n != 2 || err != nil {
        return errors.New("write rsp: " + err.Error())
    }

    return nil
}

func Socks5Connect(client net.Conn) (net.Conn, error) {
    buf := make([]byte, 256)

    n, err := io.ReadFull(client, buf[:4])
    if n != 4 {
        return nil, errors.New("read header: " + err.Error())
    }

    ver, cmd, _, atyp := buf[0], buf[1], buf[2], buf[3]
    if ver != 5 || cmd != 1 {
        return nil, errors.New("invalid ver/cmd")
    }

    addr := ""
    switch atyp {
    case 1:
        n, err = io.ReadFull(client, buf[:4])
        if n != 4 {
            return nil, errors.New("invalid IPv4: " + err.Error())
        }
        addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])

    case 3:
        n, err = io.ReadFull(client, buf[:1])
        if n != 1 {
            return nil, errors.New("invalid hostname: " + err.Error())
        }
        addrLen := int(buf[0])

        n, err = io.ReadFull(client, buf[:addrLen])
        if n != addrLen {
            return nil, errors.New("invalid hostname: " + err.Error())
        }
        addr = string(buf[:addrLen])

    case 4:
        return nil, errors.New("IPv6: no supported yet")

    default:
        return nil, errors.New("invalid atyp")
    }

    n, err = io.ReadFull(client, buf[:2])
    if n != 2 {
        return nil, errors.New("read port: " + err.Error())
    }
    port := binary.BigEndian.Uint16(buf[:2])

    destAddrPort := fmt.Sprintf("%s:%d", addr, port)
    dest, err := net.Dial("tcp", destAddrPort)
    if err != nil {
        return nil, errors.New("dial dst: " + err.Error())
    }

    n, err = client.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
    if err != nil {
        dest.Close()
        return nil, errors.New("write rsp: " + err.Error())
    }

    return dest, nil
}

func Socks5Forward(client, target net.Conn) {

    forward := func(src, dest net.Conn) {
        defer src.Close()
        defer dest.Close()

        io.Copy(src, dest)
    }

    go forward(client, target)
    go forward(target, client)
}


Launching both programs on the same machine and curl downloading the file gives the following result:

curl different file result

Did a few tests.

  1. Set the first proxy to have the same bandwidth as the machine, and curl downloads the file normally.
  2. curl downloads only through the second socks5 proxy, and set a speed limit on the second socks5 proxy, curl downloads the file normally.
  3. Both the first proxy and the second proxy set the same traffic limit, and curl downloads the file is ok!!!

Solution

  • I checked the below things. I think there is no problem in your code itself. So I decided your environment can be one of the possible causes.

    (1) Copy function itself works well. I checked it by using this code. https://go.dev/play/p/5a-T4tIaEFC

    (2) Using your added code for sock5 proxy, I checked it works well too.