当前位置:首页 > 文章列表 > Golang > Go教程 > GolangSOCKS代理设置与认证教程

GolangSOCKS代理设置与认证教程

2025-10-10 10:05:53 0浏览 收藏

在IT行业这个发展更新速度很快的行业,只有不停止的学习,才不会被行业所淘汰。如果你是Golang学习者,那么本文《Golang SOCKS代理实现与认证配置方法》就很适合你!本篇内容主要包括##content_title##,希望对大家的知识积累有所帮助,助力实战开发!

答案:SOCKS5认证流程包括客户端发送支持的认证方法,服务器选择用户密码认证(0x02)并响应,随后客户端发送用户名密码进行验证,服务器校验凭据后返回成功或失败状态,认证通过后进入连接请求阶段。

GolangSOCKS代理实现 支持认证配置

用Golang实现一个支持认证的SOCKS代理,核心在于理解SOCKS5协议的握手、认证和连接请求流程,并用Go的并发特性高效地处理网络连接和数据转发。这不仅仅是代码的堆砌,更是对网络协议细节的一种深度理解和实践。

实现SOCKS5代理,特别是带认证的,主要围绕几个关键步骤展开:监听客户端连接、处理SOCKS5协议的初始握手、根据协议协商进行用户密码认证(如果启用),最后解析客户端的连接请求并建立到目标地址的连接,双向转发数据。

解决方案

package main

import (
    "fmt"
    "io"
    "log"
    "net"
    "strconv"
    "time"
)

// SOCKS5协议常量
const (
    SOCKS5Version = 0x05
    AuthNoAuth    = 0x00
    AuthUserPass  = 0x02
    CmdConnect    = 0x01
    AtypIPv4      = 0x01
    AtypDomain    = 0x03
    AtypIPv6      = 0x04
    ReplySuccess  = 0x00
    ReplyFailure  = 0x01 // General SOCKS server failure
    ReplyAuthFail = 0x02 // Connection not allowed by rule set (used for auth fail)
)

// UserCredentials 存储用户凭据
var UserCredentials = map[string]string{
    "admin": "password123",
    "user":  "securepass",
}

func main() {
    listener, err := net.Listen("tcp", ":1080")
    if err != nil {
        log.Fatalf("Failed to listen: %v", err)
    }
    defer listener.Close()
    log.Println("SOCKS5 proxy listening on :1080")

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Printf("Failed to accept connection: %v", err)
            continue
        }
        go handleClient(conn)
    }
}

func handleClient(conn net.Conn) {
    defer conn.Close()
    conn.SetDeadline(time.Now().Add(10 * time.Second)) // 设置握手超时

    // 阶段1: 握手
    // 客户端发送: VER NMETHODS METHODS...
    buf := make([]byte, 257) // VER(1) + NMETHODS(1) + METHODS(255)
    n, err := io.ReadFull(conn, buf[:2])
    if err != nil || n != 2 {
        log.Printf("Failed to read SOCKS5 handshake initial: %v", err)
        return
    }
    if buf[0] != SOCKS5Version {
        log.Printf("Unsupported SOCKS version: %x", buf[0])
        return
    }
    nmethods := int(buf[1])
    n, err = io.ReadFull(conn, buf[2:2+nmethods])
    if err != nil || n != nmethods {
        log.Printf("Failed to read SOCKS5 methods: %v", err)
        return
    }

    // 选择认证方法
    var authMethod byte = AuthNoAuth // 默认无认证
    foundAuthMethod := false
    for i := 0; i < nmethods; i++ {
        if buf[2+i] == AuthUserPass {
            authMethod = AuthUserPass
            foundAuthMethod = true
            break
        }
    }
    // 如果客户端没提供我们支持的认证方法,我们也不支持无认证,就直接拒绝
    if !foundAuthMethod && authMethod == AuthUserPass { // 如果我们期望用户密码认证,但客户端不支持
        conn.Write([]byte{SOCKS5Version, 0xFF}) // 0xFF表示无可接受方法
        log.Println("Client did not offer supported authentication method.")
        return
    }

    // 服务器响应: VER METHOD
    _, err = conn.Write([]byte{SOCKS5Version, authMethod})
    if err != nil {
        log.Printf("Failed to write SOCKS5 method response: %v", err)
        return
    }

    // 阶段2: 认证 (如果需要)
    if authMethod == AuthUserPass {
        if !authenticate(conn) {
            log.Println("Authentication failed.")
            return // 认证失败,连接关闭
        }
        log.Println("Authentication successful.")
    }

    conn.SetDeadline(time.Now().Add(10 * time.Second)) // 重置超时

    // 阶段3: 请求
    // 客户端发送: VER CMD RSV ATYP DST.ADDR DST.PORT
    n, err = io.ReadFull(conn, buf[:4])
    if err != nil || n != 4 {
        log.Printf("Failed to read SOCKS5 request initial: %v", err)
        return
    }
    if buf[0] != SOCKS5Version || buf[2] != 0x00 { // VER 和 RSV 检查
        log.Printf("Invalid SOCKS5 request header: %x %x", buf[0], buf[2])
        return
    }
    cmd := buf[1]
    atyp := buf[3]

    var dstAddr string
    var dstPort int

    switch atyp {
    case AtypIPv4:
        n, err = io.ReadFull(conn, buf[:4]) // IPv4地址
        if err != nil || n != 4 {
            log.Printf("Failed to read IPv4 address: %v", err)
            return
        }
        dstAddr = net.IPv4(buf[0], buf[1], buf[2], buf[3]).String()
    case AtypDomain:
        n, err = io.ReadFull(conn, buf[:1]) // 域名长度
        if err != nil || n != 1 {
            log.Printf("Failed to read domain length: %v", err)
            return
        }
        domainLen := int(buf[0])
        n, err = io.ReadFull(conn, buf[1:1+domainLen]) // 域名
        if err != nil || n != domainLen {
            log.Printf("Failed to read domain: %v", err)
            return
        }
        dstAddr = string(buf[1 : 1+domainLen])
    case AtypIPv6:
        n, err = io.ReadFull(conn, buf[:16]) // IPv6地址
        if err != nil || n != 16 {
            log.Printf("Failed to read IPv6 address: %v", err)
            return
        }
        dstAddr = net.IP(buf[:16]).String()
    default:
        log.Printf("Unsupported address type: %x", atyp)
        conn.Write([]byte{SOCKS5Version, ReplyFailure, 0x00, AtypIPv4, 0, 0, 0, 0, 0, 0}) // 返回通用失败
        return
    }

    n, err = io.ReadFull(conn, buf[:2]) // 端口
    if err != nil || n != 2 {
        log.Printf("Failed to read port: %v", err)
        return
    }
    dstPort = int(buf[0])<<8 | int(buf[1])

    targetAddr := fmt.Sprintf("%s:%d", dstAddr, dstPort)
    log.Printf("Client requests to connect to: %s", targetAddr)

    // 只处理CONNECT命令
    if cmd != CmdConnect {
        log.Printf("Unsupported SOCKS command: %x", cmd)
        conn.Write([]byte{SOCKS5Version, ReplyFailure, 0x00, AtypIPv4, 0, 0, 0, 0, 0, 0})
        return
    }

    // 连接目标服务器
    targetConn, err := net.DialTimeout("tcp", targetAddr, 5*time.Second)
    if err != nil {
        log.Printf("Failed to connect to target %s: %v", targetAddr, err)
        conn.Write([]byte{SOCKS5Version, ReplyFailure, 0x00, AtypIPv4, 0, 0, 0, 0, 0, 0}) // 连接失败
        return
    }
    defer targetConn.Close()

    // 服务器响应: VER REP RSV ATYP BND.ADDR BND.PORT
    // 成功连接,返回SOCKS5成功响应
    localAddr := targetConn.LocalAddr().(*net.TCPAddr)
    bndAddr := localAddr.IP
    bndPort := localAddr.Port

    resp := []byte{SOCKS5Version, ReplySuccess, 0x00, AtypIPv4, bndAddr[0], bndAddr[1], bndAddr[2], bndAddr[3], byte(bndPort >> 8), byte(bndPort & 0xFF)}
    if bndAddr.To4() == nil { // 如果是IPv6地址,需要调整响应格式
        // 简单处理,这里假设我们只返回IPv4的绑定地址
        // 实际应根据绑定地址类型返回对应的ATYP和地址长度
        resp = []byte{SOCKS5Version, ReplySuccess, 0x00, AtypIPv4, 0, 0, 0, 0, 0, 0} // 简化为0.0.0.0
        if bndAddr.To16() != nil {
            resp = []byte{SOCKS5Version, ReplySuccess, 0x00, AtypIPv6}
            resp = append(resp, bndAddr...)
            resp = append(resp, byte(bndPort>>8), byte(bndPort&0xFF))
        }
    }
    _, err = conn.Write(resp)
    if err != nil {
        log.Printf("Failed to write SOCKS5 success response: %v", err)
        return
    }

    // 阶段4: 数据转发
    conn.SetDeadline(time.Time{}) // 移除超时
    targetConn.SetDeadline(time.Time{})
    log.Printf("Relaying data between client and %s", targetAddr)
    relay(conn, targetConn)
}

// authenticate 处理SOCKS5用户名/密码认证
func authenticate(conn net.Conn) bool {
    // 认证子协议: VER ULEN UNAME PLEN PASSWD
    buf := make([]byte, 257) // VER(1) + ULEN(1) + UNAME(255)
    n, err := io.ReadFull(conn, buf[:2])
    if err != nil || n != 2 {
        log.Printf("Auth: Failed to read initial auth header: %v", err)
        return false
    }
    if buf[0] != 0x01 { // 认证子协议版本,固定为0x01
        log.Printf("Auth: Unsupported auth sub-negotiation version: %x", buf[0])
        return false
    }

    usernameLen := int(buf[1])
    n, err = io.ReadFull(conn, buf[2:2+usernameLen])
    if err != nil || n != usernameLen {
        log.Printf("Auth: Failed to read username: %v", err)
        return false
    }
    username := string(buf[2 : 2+usernameLen])

    // PLEN PASSWD
    n, err = io.ReadFull(conn, buf[2+usernameLen : 2+usernameLen+2]) // PLEN(1) + PASSWD_INITIAL_BYTE(1)
    if err != nil || n != 2 {
        log.Printf("Auth: Failed to read password length initial: %v", err)
        return false
    }
    passwordLen := int(buf[2+usernameLen])
    n, err = io.ReadFull(conn, buf[2+usernameLen+1 : 2+usernameLen+1+passwordLen])
    if err != nil || n != passwordLen {
        log.Printf("Auth: Failed to read password: %v", err)
        return false
    }
    password := string(buf[2+usernameLen+1 : 2+usernameLen+1+passwordLen])

    // 验证凭据
    if UserCredentials[username] == password {
        conn.Write([]byte{0x01, ReplySuccess}) // 认证成功
        return true
    } else {
        conn.Write([]byte{0x01, ReplyAuthFail}) // 认证失败
        return false
    }
}

// relay 在两个连接之间双向转发数据
func relay(left, right net.Conn) {
    ch := make(chan struct{})
    go func() {
        _, _ = io.Copy(left, right)
        ch <- struct{}{}
    }()
    go func() {
        _, _ = io.Copy(right, left)
        ch <- struct{}{}
    }()
    <-ch // 等待一个方向的传输结束
    // 另一个方向可能还在传输,但通常一个方向关闭,另一个也会很快关闭
    // 简单起见,这里不再等待第二个ch,因为io.Copy遇到EOF或错误都会返回
    // 实际生产环境可能需要更精细的关闭逻辑
}

SOCKS5协议认证流程是怎样的?

SOCKS5协议的认证流程,说实话,一开始看协议文档可能会觉得有点枯燥,但一旦跑起来,你会发现它其实挺精巧的。它不是一上来就让你输密码,而是先有个“协商”阶段。

具体来说,当客户端尝试连接SOCKS5代理时,第一件事是发送一个初始握手包。这个包里包含了SOCKS版本(固定是0x05),然后是客户端支持的认证方法数量,紧接着就是一串它能接受的认证方法列表。比如,它可能会说:“我支持无认证(0x00),也支持用户名/密码认证(0x02)。”

代理服务器收到这个包后,会从客户端提供的列表中选择一个它也支持且偏好的认证方法。如果代理配置了必须使用用户名/密码认证,而客户端又提供了这个选项,那代理就会选择0x02。选定之后,代理会回一个响应包给客户端,告诉它:“嘿,我们决定用这个方法认证。”

如果协商的结果是“无认证”(0x00),那接下来的事情就简单了,直接进入请求阶段。但如果协商结果是“用户名/密码认证”(0x02),那么协议就会进入一个独立的认证子协议

在这个子协议里,客户端会发送另一个包,里面包含了用户名和密码。这个包也有自己的格式:一个子协议版本号(通常是0x01),然后是用户名的长度和用户名本身,接着是密码的长度和密码本身。代理服务器拿到这些信息后,就会去验证这对凭据是否有效。

验证成功,代理会回复一个“认证成功”的包;验证失败,则回复“认证失败”。只有认证成功了,客户端才能继续发送它真正的连接请求(比如,我想连接到哪个网站的哪个端口)。这个设计挺好的,它把认证和实际的连接请求分开了,让协议更灵活。

如何在Golang中处理SOCKS5的连接请求和数据转发?

在Golang里处理SOCKS5的连接请求和数据转发,我觉得最棒的地方就是它对并发和网络IO的原生支持。net包和io包简直是为这种场景量身定做的。

当你完成认证(或者跳过认证)之后,客户端就会发送它的连接请求包。这个包里包含了SOCKS版本、命令(通常是CONNECT,也就是连接一个远程地址)、保留字节、地址类型(IPv4、域名还是IPv6),以及目标地址和端口。

在Go里面,你需要做的就是:

  1. 解析请求: 读入客户端发送的字节流,根据SOCKS5协议的规定,逐字节地解析出目标地址类型、目标地址和目标端口。这里需要注意处理不同地址类型(IPv4、域名、IPv6)的字节长度差异。域名解析的话,Go的net.Dial通常能自动处理,但你需要先把域名字符串提取出来。
  2. 建立目标连接: 一旦你拿到了目标地址和端口,就可以使用net.Dial或者更推荐的net.DialTimeout来尝试连接到这个目标。使用DialTimeout是个好习惯,可以避免因为目标服务器无响应而导致代理线程长时间阻塞。
  3. 发送响应: 无论连接目标成功还是失败,你都需要根据SOCKS5协议,给客户端发送一个响应包,告诉它连接的结果。成功就是0x00,失败则对应不同的错误码。
  4. 数据转发: 这是整个代理的核心。一旦客户端和目标服务器都连接上了,你就需要把客户端发来的数据转发给目标服务器,同时把目标服务器返回的数据转发回给客户端。Go的io.Copy函数在这里简直是神器,它能高效地在两个io.Readerio.Writer之间复制数据。你通常会启动两个goroutine,一个负责客户端 -> 目标,另一个负责目标 -> 客户端,这样就能实现双向的数据流。当任何一边的连接关闭或出错时,io.Copy会自动返回,从而让对应的goroutine结束,最终导致整个连接的关闭。这种方式简洁高效,避免了手动管理大量缓冲区的复杂性。

当然,整个过程中,错误处理是必不可少的,任何一步出错都应该及时记录日志并关闭连接,避免资源泄露。

如何为SOCKS代理添加用户认证功能?

给SOCKS代理添加用户认证功能,其实就是把前面说的SOCKS5协议的“认证子协议”那部分逻辑实现出来。对我来说,这就像是给你的房子加一道门禁,不是谁都能进来的。

核心思路是:

  1. 定义用户凭据存储: 最简单的方式,你可以用一个Go的map[string]string来存储用户名:密码的键值对。例如,map[string]string{"admin": "password123", "user": "securepass"}。当然,在实际生产环境中,这些凭据应该存储在配置文件、环境变量、数据库,甚至更安全的秘密管理服务中,并且密码绝对不能是明文,至少要经过哈希处理(虽然SOCKS5协议本身传输是明文,但代理内部存储要安全)。
  2. 实现认证逻辑:
    • 读取认证请求: 当SOCKS5握手阶段协商确定使用“用户名/密码认证”后,你的代理就需要从客户端连接中读取下一个认证请求包。这个包的格式是固定的:一个版本号(0x01),然后是用户名的长度、用户名本身,接着是密码的长度、密码本身。你需要精确地读取这些字节,并解析出用户名和密码字符串。
    • 验证凭据: 拿到用户名和密码后,你就拿它们去和你的预设存储进行比对。如果UserCredentials[username] == password,那么认证就成功了。
    • 发送认证响应: 无论验证结果如何,你都必须按照SOCKS5认证子协议的规定,给客户端发送一个响应:
      • 认证成功:发送0x01(版本号)和0x00(成功状态码)。
      • 认证失败:发送0x010x01(失败状态码)。
    • 处理认证失败: 如果认证失败,你的代理应该立即关闭这个客户端连接,不让它进行后续的连接请求。这就像门禁系统,刷卡失败就无法开门。

这个认证功能虽然增加了代码量,但它极大地提升了代理的安全性,避免了任何人都能通过你的代理访问网络,这在很多场景下都是一个基本要求。

到这里,我们也就讲完了《GolangSOCKS代理设置与认证教程》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于golang,认证,数据转发,SOCKS代理,SOCKS5协议的知识点!

微信收款码申请教程:个人如何开通收款功能微信收款码申请教程:个人如何开通收款功能
上一篇
微信收款码申请教程:个人如何开通收款功能
Java图书借阅查询功能实现教程
下一篇
Java图书借阅查询功能实现教程
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ChatExcel酷表:告别Excel难题,北大团队AI助手助您轻松处理数据
    ChatExcel酷表
    ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
    3182次使用
  • Any绘本:开源免费AI绘本创作工具深度解析
    Any绘本
    探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
    3393次使用
  • 可赞AI:AI驱动办公可视化智能工具,一键高效生成文档图表脑图
    可赞AI
    可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
    3424次使用
  • 星月写作:AI网文创作神器,助力爆款小说速成
    星月写作
    星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
    4528次使用
  • MagicLight.ai:叙事驱动AI动画视频创作平台 | 高效生成专业级故事动画
    MagicLight
    MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
    3802次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码