GO语言工程实践课后作业:实现思路、代码以及路径记录 | 青训营

100 阅读6分钟

使用Golang实现简易的Socks5代理服务器

引言

网络代理是一种常见的网络技术,用于通过中间服务器转发网络请求和响应。Socks5代理是一种广泛使用的网络代理协议,它支持多种身份验证方法和灵活的目标地址类型。在本文中,我们将详细介绍如何使用Golang编程语言实现一个简单的Socks5代理服务器,并解释其代码实现。

1. 了解Socks5代理协议

在开始实现Socks5代理服务器之前,让我们简要了解Socks5协议的工作原理和相关概念。

  • Socks5协议版本号: Socks5协议的版本号为0x05。
  • 身份验证方法: Socks5允许客户端和服务器协商使用的身份验证方法。其中,0x00表示无需身份验证,0x02表示使用用户名和密码进行身份验证。
  • 目标地址类型: Socks5支持三种目标地址类型,分别是IPv4地址(0x01)、域名(0x03)和IPv6地址(0x04)。

2. 实现Socks5代理服务器

现在我们来看一下如何使用Golang实现一个简单的Socks5代理服务器。

首先,我们需要导入所需的包,并定义一些常量,包括Socks5协议的版本号、命令类型和地址类型。

package main

import (
	"bufio"
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
)

const (
	socks5Ver  = 0x05
	cmdBind    = 0x01
	atypeIPV4  = 0x01
	atypeHOST  = 0x03
	atypeIPV6  = 0x04
)

接下来,我们在main函数中创建一个TCP监听器,并在循环中接受客户端的连接请求。每当有新的客户端连接时,我们会启动一个独立的goroutine来处理该客户端的请求。


func main() {
	server, err := net.Listen("tcp", "127.0.0.1:1080")
	if err != nil {
		panic(err)
	}
	defer server.Close()

	log.Println("Socks5 proxy server is listening on 127.0.0.1:1080")

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

handleClient函数中,我们处理与单个客户端的通信。首先,我们从连接中获取一个bufio.Reader以便逐行读取数据。然后,我们调用auth函数进行Socks5认证,接着调用connect函数与目标服务器建立连接。

func handleClient(client net.Conn) {
	defer client.Close()
	reader := bufio.NewReader(client)

	if err := auth(reader, client); err != nil {
		log.Printf("Client %v authentication failed: %v", client.RemoteAddr(), err)
		return
	}

	if err := connect(reader, client); err != nil {
		log.Printf("Client %v connection failed: %v", client.RemoteAddr(), err)
		return
	}
}

auth函数中,我们解析客户端发送的认证方法列表。根据协商结果,我们返回选择的认证方法给客户端。


func auth(reader *bufio.Reader, conn net.Conn) error {
	ver, err := reader.ReadByte()
	if err != nil {
		return fmt.Errorf("read ver failed:%w", err)
	}
	if ver != socks5Ver {
		return fmt.Errorf("not supported ver:%v", ver)
	}

	methodSize, err := reader.ReadByte()
	if err != nil {
		return fmt.Errorf("read methodSize failed:%w", err)
	}

	methods := make([]byte, methodSize)
	_, err = io.ReadFull(reader, methods)
	if err != nil {
		return fmt.Errorf("read methods failed:%w", err)
	}

	_, err = conn.Write([]byte{socks5Ver, 0x00})
	if err != nil {
		return fmt.Errorf("write failed:%w", err)
	}
	return nil
}

最后,connect函数负责解析客户端发送的连接请求头部,并连接到目标服务器。如果连接成功,它会告知客户端连接建立成功,然后在新的goroutine中将客户端和目标服务器连接起来。

func connect(reader *bufio.Reader, conn net.Conn) error {
	buf := make([]byte, 4)
	_, err := io.ReadFull(reader, buf)
	if err != nil {
		return fmt.Errorf("read header failed:%w", err)
	}
	ver, cmd, atyp := buf[0], buf[1], buf[3]
	if ver != socks5Ver {
		return fmt.Errorf("not supported ver:%v", ver)
	}
	if cmd != cmdBind {
		return fmt.Errorf("not supported cmd:%v", cmd)
	}

	var addr string
	switch atyp {
	case atypeIPV4:
		_, err = io.ReadFull(reader, buf)
		if err != nil {
			return fmt.Errorf("read atyp failed:%w", err)
		}
		addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
	case atypeHOST:
		hostSize, err := reader.ReadByte()
		if err != nil {
			return fmt.Errorf("read hostSize failed:%w", err)
		}
		host := make([]byte, hostSize)
		_, err = io.ReadFull(reader, host)
		if err != nil {
			return fmt.Errorf("read host failed:%w", err)
		}
		addr = string(host)
	case atypeIPV6:
		return errors.New("IPv6: no supported yet")
	default:
		return errors.New("invalid atyp")
	}
	_, err = io.ReadFull(reader, buf[:2])
	if err != nil {
		return fmt.Errorf("read port failed:%w", err)
	}
	port := binary.BigEndian.Uint16(buf[:2])

	dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
	if err != nil {
		return fmt.Errorf("dial dst failed:%w", err)
	}
	defer dest.Close()

	_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
	if err != nil {
		return fmt.Errorf("write failed: %w", err)
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go func() {
		_, _ = io.Copy(dest, reader)
		cancel()
	}()
	go func() {
		_, _ = io.Copy(conn, dest)
		cancel()
	}()

	<-ctx.Done()
	return nil
}

3. 使用示例

现在我们已经实现了一个简单的Socks5代理服务器。我们可以在终端中运行该程序,并配置浏览器或其他应用程序使用该代理服务器。

$ go run main.go

终端会显示"Socks5 proxy server is listening on 127.0.0.1:1080",表示代理服务器已经在本地监听1080端口。接下来,我们只需要在浏览器或其他应用程序中将代理设置为"127.0.0.1:1080"即可使用Socks5代理。

4. 结论

在本文中,我们成功实现了一个简单的Socks5代理服务器,通过Golang编程语言,使得我们能够了解Socks5代理协议的基本工作原理,并学习了如何利用Golang的强大功能来编写一个高效的网络代理服务器。

Socks5代理协议是一种广泛使用的网络代理协议,其支持多种身份验证方法和灵活的目标地址类型,使其在现代网络应用中广受欢迎。通过本文,我们深入了解了Socks5协议的版本号、身份验证方法和目标地址类型,这些是实现代理服务器所必须的基本知识。

在实现过程中,我们使用了Golang的标准库中的net和io等包,这些包提供了丰富的功能来简化网络通信和数据处理的过程。同时,通过利用goroutine的并发特性,我们能够同时处理多个客户端的连接请求,提高了代理服务器的并发处理能力。

我们的Socks5代理服务器在监听端口后,能够有效地接收来自客户端的连接请求,并在认证成功后,将客户端请求转发到目标服务器,实现了简单的请求转发功能。在实际应用中,我们可以进一步完善代理服务器的功能,例如加入日志记录、访问控制、流量限制等功能,以提高代理服务器的安全性和性能。

使用Socks5代理服务器可以帮助用户绕过网络限制,访问被封锁的网站,同时也可以增加用户的网络隐私和安全性。然而,我们也要注意代理服务器可能会被滥用,用于进行非法活动,因此在部署代理服务器时,务必要遵守法律法规,并确保代理服务器的安全性。

总体而言,本文的实现示例为我们提供了一个很好的起点,使我们能够更深入地理解Socks5代理协议和Golang编程语言。通过不断优化和扩展代理服务器的功能,我们可以构建出更加强大和稳定的网络代理系统,为用户提供更好的网络体验和安全保障。