This document provides instructions to create a dual-protocol (HTTP and SOCKS5) proxy server in Go. Follow the steps below to create the necessary files and implement the logic.
First, create the project structure. Open your terminal and run these commands:
mkdir -p test_server
touch go.mod config.yaml main.go test_server/main.goNow, you will implement the logic for each file.
- Declare a Go module named
proxy. - Specify Go version
1.24.2. - Add a dependency on
gopkg.in/yaml.v2versionv2.4.0.
- Create a YAML file.
- Add a top-level key named
allowed_ips. - The value for
allowed_ipsshould be a list of strings, where each string is an IP address. - For local testing, add
"127.0.0.1"and"::1"to the list.
This is the core of the proxy server.
-
Package and Imports:
- Use package
main. - Import necessary packages for networking (
net,net/http), I/O (io,io/ioutil,bufio), logging (log), command-line flags (flag), string/number conversion (strings,strconv), binary data handling (encoding/binary), and YAML parsing (gopkg.in/yaml.v2).
- Use package
-
Constants and Structs:
- Define constants for the proxy port (
"8080") and SOCKS5 protocol values (e.g.,socks5Version = 0x05). - Create a
Configstruct with anAllowedIPsfield (a slice of strings) to match the structure ofconfig.yaml.
- Define constants for the proxy port (
-
Configuration Loading:
- Implement a
loadConfig(path string)function that reads theconfig.yamlfile, unmarshals it into theConfigstruct, and returns amap[string]boolfor efficient IP address lookups.
- Implement a
-
Port Availability:
- Implement an
isPortAvailable(port string)function that checks if a given TCP port is free to listen on.
- Implement an
-
Main Function:
- Add a boolean command-line flag (
-dor-debug) to enable debug logging. - Check if the proxy port is available; if not, log a fatal error.
- Call
loadConfigto load the allowed IPs. - Create a TCP listener on the proxy port.
- Enter an infinite loop to accept incoming connections (
listener.Accept()). - For each new connection, spawn a new goroutine that calls a
handleConnectionfunction.
- Add a boolean command-line flag (
-
Connection Handling:
- Implement
handleConnection(conn net.Conn, ...):- Verify the client's remote IP address is present in the allowed IPs map. If not, close the connection.
- Use
bufio.Reader.Peek(1)to look at the first byte of the incoming data without consuming it. - If the first byte matches the SOCKS5 version constant, pass the connection to a
handleSocks5function. - Otherwise, pass it to an
handleHTTPfunction.
- Implement
-
HTTP Handler:
- Implement
handleHTTP(...):- Read the full HTTP request from the client connection.
- Establish a new TCP connection to the destination server specified in the request's
Hostheader. - If the request method is
CONNECT(for HTTPS), respond to the client withHTTP/1.1 200 Connection established. - If it's any other method, write the original request to the destination server.
- Use
io.Copyin separate goroutines to relay data in both directions between the client and the destination server.
- Implement
-
SOCKS5 Handler:
- Implement
handleSocks5(...):- Perform the SOCKS5 handshake: read the version and method count, then select the "No Authentication" method (
0x00) and send it back to the client. - Read the client's request details (command, address type, destination address, and port).
- Handle different address types (IPv4, domain name, IPv6) to correctly parse the destination address.
- Establish a TCP connection to the parsed destination address and port.
- Send a SOCKS5 success reply back to the client.
- Use
io.Copyto relay data between the client and the destination.
- Perform the SOCKS5 handshake: read the version and method count, then select the "No Authentication" method (
- Implement
This is a simple server to help test the proxy.
-
Package and Imports:
- Use package
main. - Import packages for logging (
log), networking (net,net/http), and the operating system (os).
- Use package
-
Main Function:
- Define a constant for the server's port (e.g.,
"8081"). - Optionally, use the
isPortAvailablefunction from the proxy to check if the port is free. - Create a simple file server using
http.FileServer(http.Dir(".")). - Start the HTTP server to listen on the specified port.
- Define a constant for the server's port (e.g.,
-
Run the Test Server:
go run test_server/main.go
-
Run the Proxy Server: In a separate terminal:
go run main.go
-
Test the Proxy: In a third terminal, use
curlto make requests through the proxy.- HTTP Test:
curl -x http://127.0.0.1:8080 http://127.0.0.1:8081/
- SOCKS5 Test:
curl --socks5 127.0.0.1:8080 http://127.0.0.1:8081/
- HTTP Test:
Both curl commands should successfully connect to the test server through your proxy and return a listing of the files in the test_server directory.