Simple SOCKS5 Proxy Server By Golang

A simple SOCKS5 proxy server, implemented in Golang, and deployed using Docker Compose.

Simple SOCKS5 Proxy Server Generated by Grok

Prerequisites

Directory Structure

your_project/
├── main.go
├── Dockerfile
└── docker-compose.yml

main.go Code

The default listening port is 1080. To change the port, modify the mapping in docker-compose.yml.

package main

import (
    "encoding/binary"
    "fmt"
    "io"
    "log"
    "net"
    "sync"
)

// Global variables for session management
var (
    // allowedClients stores allowed client IPs and their active session counts
    allowedClients sync.Map // map[string]interface{}, value is *int
    // destToClient maps destination addresses to client UDP addresses
    destToClient sync.Map // map[string]string
)

func main() {
    // Start TCP listener
    go func() {
        listener, err := net.Listen("tcp", ":1080")
        if err != nil {
            log.Fatal("Failed to start TCP listener: ", err)
        }
        defer listener.Close()
        log.Println("TCP serverK server started on :1080")

        for {
            conn, err := listener.Accept()
            if err != nil {
                log.Println("Failed to accept TCP connection: ", err)
                continue
            }
            go handleConnection(conn)
        }
    }()

    // Start UDP listener
    udpAddr, err := net.ResolveUDPAddr("udp", ":1080")
    if err != nil {
        log.Fatal("Failed to resolve UDP address: ", err)
    }
    udpConn, err := net.ListenUDP("udp", udpAddr)
    if err != nil {
        log.Fatal("Failed to start UDP listener: ", err)
    }
    defer udpConn.Close()
    log.Println("UDP server started on :1080")

    buf := make([]byte, 65536)
    for {
        n, clientAddr, err := udpConn.ReadFromUDP(buf)
        if err != nil {
            log.Println("Failed to read UDP data: ", err)
            continue
        }
        go handleUDPPacket(udpConn, clientAddr, buf[:n])
    }
}

// handleConnection handles TCP connections
func handleConnection(conn net.Conn) {
    defer conn.Close()

   // Read client greeting
    buf := make([]byte, 2)
    _, err := io.ReadFull(conn, buf)
    if err != nil {
        log.Println("Failed to read greeting: ", err)
        return
    }
    if buf[0] != 0x05 {
        log.Println("Unsupported protocol version: ", buf[0])
        return
    }
    nmethods := buf[1]
    methods := make([]byte, nmethods)
    _, err = io.ReadFull(conn, methods)
    if err != nil {
        log.Println("Failed to read authentication methods: ", err)
        return
    }

    // Reply with no authentication required
    _, err = conn.Write([]byte{0x05, 0x00})
    if err != nil {
        log.Println("Failed to send authentication reply: ", err)
        return
    }

    // Read client request
    buf = make([]byte, 4)
    _, err = io.ReadFull(conn, buf)
    if err != nil {
        log.Println("Failed to read request:", err)
        return
    }
    if buf[0] != 0x05 {
        log.Println("Unsupported protocol version: ", buf[0])
        return
    }
    cmd := buf[1]
    if cmd != 0x01 && cmd != 0x03 {
        log.Println("Unsupported command: ", cmd)
        conn.Write([]byte{0x05, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
        return
    }
    atyp := buf[3]
    var addr string
    switch atyp {
    case 0x01: // IPv4
        ip := make([]byte, 4)
        _, err = io.ReadFull(conn, ip)
        if err != nil {
            log.Println("Failed to read IPv4 address: ", err)
            return
        }
        addr = net.IP(ip).String()
    case 0x03: // Domain name
        lenByte := make([]byte, 1)
        _, err = io.ReadFull(conn, lenByte)
        if err != nil {
            log.Println("Failed to read domain length: ", err)
            return
        }
        domainLen := lenByte[0]
        domain := make([]byte, domainLen)
        _, err = io.ReadFull(conn, domain)
        if err != nil {
            log.Println("Failed to read domain: ", err)
            return
        }
        addr = string(domain)
    case 0x04: // IPv6
        ip := make([]byte, 16)
        _, err = io.ReadFull(conn, ip)
        if err != nil {
            log.Println("Failed to read IPv6 address: ", err)
            return
        }
        addr = net.IP(ip).String()
    default:
        log.Println("Unsupported address type: ", atyp)
        conn.Write([]byte{0x05, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
        return
    }
    portBytes := make([]byte, 2)
    _, err = io.ReadFull(conn, portBytes)
    if err != nil {
        log.Println("Failed to read port: ", err)
        return
    }
    port := binary.BigEndian.Uint16(portBytes)

    if cmd == 0x01 { // CONNECT
        // Connect to the target server
        destConn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", addr, port))
        if err != nil {
            log.Println("Failed to connect to target: ", err)
            conn.Write([]byte{0x05, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
            return
        }
        defer destConn.Close()

        // Send success reply
        reply := []byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
        _, err = conn.Write(reply)
        if err != nil {
            log.Println("Failed to send success reply: ", err)
            return
        }

        // Relay data
        go io.Copy(destConn, conn)
        io.Copy(conn, destConn)
    } else if cmd == 0x03 { // UDP ASSOCIATE
        clientIP := conn.RemoteAddr().(*net.TCPAddr).IP.String()
        val, _ := allowedClients.LoadOrStore(clientIP, new(int))
        count := val.(*int)
        *count++
        defer func() {
            *count--
            if *count == 0 {
                allowedClients.Delete(clientIP)
            }
        }()

        // Send UDP address reply
        localAddr := conn.LocalAddr().(*net.TCPAddr)
        reply := []byte{0x05, 0x00, 0x00}
        if localAddr.IP.To4() != nil {
            reply = append(reply, 0x01)
            reply = append(reply, localAddr.IP.To4()...)
        } else {
            reply = append(reply, 0x04)
            reply = append(reply, localAddr.IP.To16()...)
        }
        reply = append(reply, byte(localAddr.Port>>8), byte(localAddr.Port&0xff))
        _, err = conn.Write(reply)
        if err != nil {
            log.Println("Failed to send UDP ASSOCIATE reply: ", err)
            return
        }

        // Keep TCP connection open
        buf := make([]byte, 1)
        _, err = conn.Read(buf)
        if err != nil {
            log.Println("TCP connection closed: ", err)
        }
    }
}

// handleUDPPacket handles UDP packets
func handleUDPPacket(udpConn *net.UDPConn, srcAddr *net.UDPAddr, data []byte) {
    clientIP := srcAddr.IP.String()
    if _, ok := allowedClients.Load(clientIP); !ok {
        log.Println("UDP packet from unauthorized client: ", clientIP)
        return
    }

    // Check if it's a SOCKS5 UDP packet from the client
    if len(data) < 3 || binary.BigEndian.Uint16(data[0:2]) != 0 || data[2] != 0 {
        // Response from the target server
        destKey := srcAddr.String()
        if val, ok := destToClient.Load(destKey); ok {
            clientUDPAddrStr := val.(string)
            clientUDPAddr, err := net.ResolveUDPAddr("udp", clientUDPAddrStr)
            if err != nil {
                log.Println("Failed to resolve client UDP address: ", err)
                return
            }
            // Encapsulate SOCKS5 header
            response := []byte{0x00, 0x00, 0x00} // RSV and FRAG
            if ip := srcAddr.IP.To4(); ip != nil {
                response = append(response, 0x01)
                response = append(response, ip...)
            } else if ip := srcAddr.IP.To16(); ip != nil {
                response = append(response, 0x04)
                response = append(response, ip...)
            } else {
                log.Println("Unsupported IP type")
                return
            }
            response = append(response, byte(srcAddr.Port>>8), byte(srcAddr.Port&0xff))
            response = append(response, data...)
            _, err = udpConn.WriteToUDP(response, clientUDPAddr)
            if err != nil {
                log.Println("Failed to send response to client: ", err)
            }
        }
        return
    }

    // Request from the client
    atyp := data[3]
    var destAddr string
    var destPort uint16
    var offset int
    switch atyp {
    case 0x01: // IPv4
        if len(data) < 10 {
            log.Println("Invalid UDP packet")
            return
        }
        destAddr = net.IP(data[4:8]).String()
        destPort = binary.BigEndian.Uint16(data[8:10])
        offset = 10
    case 0x03: // Domain name
        if len(data) < 5 {
            log.Println("Invalid UDP packet")
            return
        }
        domainLen := data[4]
        if len(data) < int(5+domainLen+2) {
            log.Println("Invalid UDP packet")
            return
        }
        domain := string(data[5 : 5+domainLen])
        destAddr = domain
        destPort = binary.BigEndian.Uint16(data[5+domainLen : 7+domainLen])
        offset = int(7 + domainLen)
    case 0x04: // IPv6
        if len(data) < 22 {
            log.Println("Invalid UDP packet")
            return
        }
        destAddr = net.IP(data[4:20]).String()
        destPort = binary.BigEndian.Uint16(data[20:22])
        offset = 22
    default:
        log.Println("Unsupported address type: ", atyp)
        return
    }
    payload := data[offset:]

    // Send data to the target
    destUDPAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", destAddr, destPort))
    if err != nil {
        log.Println("Failed to resolve target UDP address: ", err)
        return
    }
    _, err = udpConn.WriteToUDP(payload, destUDPAddr)
    if err != nil {
        log.Println("Failed to send data to target: ", err)
        return
    }

    // Record mapping from target to client
    destKey := destUDPAddr.String()
    clientKey := srcAddr.String()
    destToClient.Store(destKey, clientKey)
}

Initialize Go Module and Download Dependencies

Run the following commands:

go mod init socks5-server 
go mod tidy

Dockerfile

# Use the official Golang image as the base image
FROM golang:1.20-alpine

# Set the working directory
WORKDIR /app

# Copy all files from the current directory to /app in the container
COPY . .

# Build the Golang application
RUN go build -o socks5-server

# Run the compiled binary
CMD ["./socks5-server"]

docker-compose.yml

services:
  socks5-server:  # Service name
    build:
      context: .  # Build context is the current directory
      dockerfile: Dockerfile  # Specify the Dockerfile to use
    ports:
      - "1080:1080"  # Map host port 1080 to container port 1080
      - "1080:1080/udp"

Starting the Server

docker compose up -d