go 实现的socks5服务器端

benZ · 2017-12-07 08:09:16 · 7922 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2017-12-07 08:09:16 的主题,其中的信息可能已经有所发展或是发生改变。

#####用go写的socks5服务器端代码:

1.默认只支持用户名密码的认证方式(username:abc , password:123),监听 tcp 8083 端口;

2.CMD支持TCP、UDP,不支持BIND,需要的可自行再实现;

3.支持ipv4、domain,目前不支持 ipv6;

4.建议客户端使用 proxycap, proxycap、SocksCap64 测试过能很好支持。(玩LOL,正常游戏)建议客户端不设置服务器端做DNS解析,效率相对会慢些;

5.注意服务端 防火墙 tcp 8083 端口开放及UDP协议全开放;

6.使用时有bug或问题欢迎留言;

7.想尝试用 go 写个简单的客户端,支持 lsp 劫持,配合服务器端做代理,不清楚有什么方法或 包 实现,请路过的大牛懂的请赐教!

代码部分 //socks5协议文档: http://www.ietf.org/rfc/rfc1928.txt.pdf

main.go:

package main

import (
    "bytes"
    "encoding/binary"
    "errors"
    "fmt"
    "log"
    "net"
    "sync"
    "time"
)

var (
    tcpConnId uint64
    udpConnId uint64
)

type proxy struct {
    conn *net.TCPConn
}

type uServ struct {
    prefix        string
    sigQuit       chan bool
    clientUdpAddr *net.UDPAddr
    dstMap        map[string]string
    sync.RWMutex
}

func main() {
    // Listen Tcp, support ipv4 only. @20171204
    service := "0.0.0.0:8083"
    tcpAddr, err := net.ResolveTCPAddr("tcp", service)
    errCheckWithFatal("ResolveTcpAddr err:%v\n", err)
    tcpListener, err := net.ListenTCP("tcp", tcpAddr)
    errCheckWithFatal("ListenTCP err:%v\n", err)

    for {
        tcpConn, err := tcpListener.AcceptTCP()
        errCheckWithPrint("Warn >>> AcceptTCP err:%v\n", err)
        if err != nil {
            continue
        }

        proxy := NewProxy(tcpConn)
        go proxy.run()

    }
}

func NewProxy(conn *net.TCPConn) *proxy {
    return &proxy{
        conn: conn,
    }
}

func (p *proxy) run() {
    defer p.conn.Close()
    remoteAddrStr := p.conn.RemoteAddr().String()

    // read
    buf := make([]byte, 1024)
    n, err := p.conn.Read(buf[0:])
    errCheckWithPrint("Error >>> p.conn.Read err:%v\n", err)
    if err != nil {
        return
    }

    // get data bytes
    b := buf[0:n]

    //check Methods
    if !p.isMethodsOk(b) {
        log.Printf("WARN >>> from %s request methods invalid.\n", remoteAddrStr)
        return
    }

    n, err = p.conn.Read(buf[0:])
    errCheckWithPrint("Error >>> p.conn.Read err:%v\n", err)
    if err != nil {
        return
    }
    b = buf[0:n]
    // check username/password
    if !p.isAuthOk(b) {
        log.Printf("WARN >>> from %s auth fail.\n", remoteAddrStr)
        return
    }

    // resolve proxy CMD(tcp or udp)
    /*
        +----+-----+-------+------+----------+--------+
        |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
        +----+-----+-------+------+----------+--------+
        |  1 |  1  |X’00’|  1   | Variable |     2    |
        +----+-----+-------+------+----------+--------+
    */
    n, err = p.conn.Read(buf[0:])
    errCheckWithPrint("Error >>> p.conn.Read err:%v\n", err)
    if err != nil {
        return
    }
    b = buf[0:n]

    if !p.isCmdSupport(b[0:2]) {
        return
    }

    if !p.isAtypSupport(b[3:4]) {
        return
    }

    dstAddr, dstPort := p.getAddrPort(b[3:])
    if dstAddr == "" {
        log.Printf("WARN >>> get NULL dstAddr,return.\n")
        return
    }

    switch b[1] {
    case 0x01: //CONNECT -> TCP
        p.tcpProxy(dstAddr, dstPort)
        return
    case 0x02:
        log.Println("WARN >>> get BIND command, not support.")
    case 0x03: //UDP
        p.udpProxy(dstAddr, dstPort)
        return
    default:
        return
    }
}

func (p *proxy) isMethodsOk(b []byte) bool {
    // username/password only.
    /*
        +----+----------+----------+
        |VER | NMETHODS |  METHODS |
        +----+----------+----------+
        |  1 |     1    | 1 to 255 |
        +----+----------+----------+
    */
    if b[0] != 0x05 || (b[1] == 0x01 && b[2] != 0x02) {
        p.conn.Write([]byte{0x05, 0xff})
        return false
    }
    p.conn.Write([]byte{0x05, 0x02})
    return true
}

func (p *proxy) isAuthOk(b []byte) bool {
    //0x01 | 用户名长度(1字节)| 用户名(长度根据用户名长度域指定) | 口令长度(1字节) | 口令(长度由口令长度域指定)

    // b[0] some software use 0x01,but base protocal must use 0x05,so keeps it here.
    b0 := b[0]

    nameLens := int(b[1])
    name := string(b[2 : 2+nameLens])

    passLens := int(b[2+nameLens])
    pass := string(b[2+nameLens+1 : 2+nameLens+1+passLens])

    //auth lens
    /*
        if nameLens != 32 || passLens != 48 {
            p.conn.Write([]byte{b0, 0xff})
            return false
        }
    */

    // auth count
    // test auth here.
    if name != "abc" || pass != "123" {
        p.conn.Write([]byte{b0, 0xff})
        return false
    }
    p.conn.Write([]byte{b0, 0x00})
    return true
}

func (p *proxy) isCmdSupport(b []byte) bool {
    if b[0] != 0x05 {
        p.conn.Write([]byte{0x05, 0x01})
        return false
    }

    switch b[1] {
    case 0x01: //CONNECT
        return true
    case 0x02: //BIND
        //not support here.
        p.conn.Write([]byte{0x05, 0x07})
        return false
    case 0x03: //UDP
        return true
    default:
        p.conn.Write([]byte{0x05, 0x07})
        return false
    }
}

func (p *proxy) isAtypSupport(b []byte) bool {

    switch b[0] {
    case 0x01: //ipv4
        return true
    case 0x03: //domain
        return true
    case 0x04: //ipv6
        p.conn.Write([]byte{0x05, 0x08})
        return false
    default:
        p.conn.Write([]byte{0x05, 0x08})
        return false
    }
}

func (p *proxy) getAddrPort(b []byte) (string, int) {
    switch b[0] {
    case 0x01: //ipv4
        addr := net.IPv4(b[1], b[2], b[3], b[4]).String()
        port := int(b[5])*256 + int(b[6])
        return addr, port

    case 0x03: //domain
        domainLens := int(b[1])
        domain := string(b[2 : 2+domainLens])
        port := int(b[2+domainLens])*256 + int(b[2+domainLens+1])
        return domain, port
    default:
        return "", 0
    }
}

func (p *proxy) tcpProxy(dstAddr string, dstPort int) {
    addr := fmt.Sprintf("%s:%d", dstAddr, dstPort)
    tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
    errCheckWithPrint("Error >>> net.ResolveTcpAddr err:%v\n", err)
    if err != nil {
        p.conn.Write([]byte{0x05, 0x03})
        return
    }

    tcpConn, err := TcpDial(nil, tcpAddr, 60*time.Second)
    format := fmt.Sprintf("Error >>> net.DialTCP %s err", addr)
    errCheckWithPrint(format+":%v\n", err)
    if err != nil {
        p.conn.Write([]byte{0x05, 0x03})
        return
    }
    defer tcpConn.Close()

    time.Sleep(10 * 1e6)
    p.conn.SetNoDelay(true)
    tcpConn.SetNoDelay(true)

    p.conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})

    tcpConnId++
    prefix := fmt.Sprintf("> tcp: %08x", tcpConnId)
    closeSig := make(chan bool, 0)
    go pipe("%s >>> %d bytes send\n", prefix, p.conn, tcpConn, closeSig)
    go pipe("%s <<< %d bytes recieve\n", prefix, tcpConn, p.conn, closeSig)
    <-closeSig
    return
}

func (p *proxy) udpProxy(clientAddr string, clientPort int) {
    //log.Printf("get client bind -> %s:%d\n", clientAddr, clientPort)

    // get localIp from p.conn
    localExitAddr, err := net.ResolveTCPAddr("tcp", p.conn.LocalAddr().String())
    errCheckWithFatal("ResolveTCPAddr err:%v\n", err)
    localExitIP := localExitAddr.IP.String()

    service := fmt.Sprintf("%s:%d", localExitIP, 0)
    udpAddr, err := net.ResolveUDPAddr("udp", service)
    errCheckWithFatal("ResolveUDPAddr err:%v\n", err)

    udpListener, err := net.ListenUDP("udp", udpAddr)
    errCheckWithFatal("ListenUDP err:%v\n", err)
    defer udpListener.Close()
    //udpListener.SetPKTINFO()

    bindUdpAddr, err := net.ResolveUDPAddr("udp", udpListener.LocalAddr().String())
    errCheckWithFatal("ResolveUDPAddr err:%v\n", err)
    bindPort := bindUdpAddr.Port

    //版本 | 代理的应答 | 保留1字节 | 地址类型 | 代理服务器地址 | 绑定的代理端口
    bindMsg := []byte{0x05, 0x00, 0x00, 0x01}
    buffer := bytes.NewBuffer(bindMsg)
    binary.Write(buffer, binary.BigEndian, localExitAddr.IP.To4())
    binary.Write(buffer, binary.BigEndian, uint16(bindPort))
    //log.Printf("Local udp BIND >>> %s:%d\n", localExitIP, bindPort)
    //log.Printf("Send udp BIND Msg >>> %v\n", buffer.Bytes())
    p.conn.Write(buffer.Bytes())

    finish := make(chan bool, 0)
    go udpKeepAlive(p.conn, finish)
    go udpServ(udpListener, finish)
    <-finish
    return
}

func errCheckWithFatal(format string, err error) {
    if err != nil {
        log.Fatalf("Fatal >>>"+format, err)
    }
    return
}

func errCheckWithPrint(format string, err error) {
    if err != nil {
        log.Printf(format, err)
    }
    return
}

func TcpDial(localAddr, remoteAddr *net.TCPAddr, timeout time.Duration) (*net.TCPConn, error) {
    returned := false
    ticker := time.NewTicker(timeout)
    defer ticker.Stop()

    type rst struct {
        tcn *net.TCPConn
        error
    }

    rst_chan := make(chan *rst, 0)
    go func() {
        tcpConn, err := net.DialTCP("tcp", localAddr, remoteAddr)
        if err != nil {
            goto Finish
        } else if returned {
            tcpConn.Close()
            return
        }
    Finish:
        rst_chan <- &rst{tcn: tcpConn, error: err}
    }()

    select {
    case <-ticker.C:
        returned = true
        return nil, errors.New("connect timeout")
    case result := <-rst_chan:
        if result.error != nil {
            return nil, result.error
        }
        return result.tcn, nil
    }
}

func pipe(format, prefix string, src, dst *net.TCPConn, closeSig chan bool) {
    buf := make([]byte, 0xff)
    for {
        n, err := src.Read(buf[0:])
        if err != nil {
            closeSig <- true
            return
        }
        b := buf[0:n]
        _, err = dst.Write(b)
        if err != nil {
            closeSig <- true
            return
        }
        //log.Printf(format, prefix, n)
    }
}

func udpKeepAlive(tcpConn *net.TCPConn, finish chan<- bool) {
    tcpConn.SetKeepAlive(true)
    buf := make([]byte, 1024)
    for {
        _, err := tcpConn.Read(buf[0:])
        if err != nil {
            finish <- true
            return
        }
    }
}

func udpServ(udpConn *net.UDPConn, finish chan<- bool) {
    udpConnId++

    udpServ := &uServ{
        prefix:  fmt.Sprintf("udp >>> #%08x", udpConnId),
        sigQuit: make(chan bool, 0),
        dstMap:  make(map[string]string),
    }

    go udpServ.read(udpConn)
    udpServ.monitor()
    finish <- true
    return
}

func (uS *uServ) monitor() {
    for {
        select {
        case <-uS.sigQuit:
            return
        }
    }
}

func (uS *uServ) read(udpConn *net.UDPConn) {
    buf := make([]byte, 2048)
    buf2 := make([]byte, 2048)
    for {
        n, udpAddr, err := udpConn.ReadFromUDP(buf[0:])
        if err != nil {
            //log.Printf("%s ReadFromUDP err:%v\n", uS.prefix, err)
            uS.sigQuit <- true
            return
        }

        if uS.clientUdpAddr == nil {
            uS.clientUdpAddr = udpAddr
        }

        b := buf[0:n]
        if udpAddr.IP.String() == uS.clientUdpAddr.IP.String() { // from client
            /*
                +----+------+------+----------+----------+----------+
                |RSV | FRAG | ATYP | DST.ADDR | DST.PORT |   DATA   |
                +----+------+------+----------+----------+----------+
                |  2 |   1  |   1  | Variable |     2    | Variable |
                +----+------+------+----------+----------+----------+
            */
            if b[2] != 0x00 {
                log.Printf("%s WARN: FRAG do not support.\n", uS.prefix)
                continue
            }

            switch b[3] {
            case 0x01: //ipv4
                dstAddr := &net.UDPAddr{
                    IP:   net.IPv4(b[4], b[5], b[6], b[7]),
                    Port: int(b[8])*256 + int(b[9]),
                }

                uS.Lock()
                if _, exist := uS.dstMap[dstAddr.String()]; !exist {
                    uS.dstMap[dstAddr.String()] = string(b[0:10])
                }
                uS.Unlock()

                udpConn.WriteToUDP(b[10:], dstAddr)
                //log.Printf("%s b-> %v\n", uS.prefix, b)
                //log.Printf("%s data-> %v\n", uS.prefix, b[10:])
            case 0x03: //domain
                domainLens := int(b[4])
                domain := string(b[5 : 5+domainLens])
                ipAddr, err := net.ResolveIPAddr("ip", domain)
                if err != nil {
                    log.Printf("%s Error -> domain %s dns query err:%v\n", uS.prefix, domain, err)
                    continue
                }
                dstAddr := &net.UDPAddr{
                    IP:   ipAddr.IP,
                    Port: int(b[5+domainLens])*256 + int(b[6+domainLens]),
                }

                uS.Lock()
                if _, exist := uS.dstMap[dstAddr.String()]; !exist {
                    uS.dstMap[dstAddr.String()] = string(b[0 : 7+domainLens])
                }
                uS.Unlock()

                udpConn.WriteToUDP(b[7+domainLens:], dstAddr)
                //log.Printf("%s b-> %v\n", uS.prefix, b)
                //log.Printf("%s data-> %v\n", uS.prefix, b[7+domainLens:])

            default:
                log.Printf("%s WARN: ATYP %v do not support.\n", uS.prefix, b[3])
                continue
            }
        } else { // from dst Server
            uS.RLock()
            if v, exist := uS.dstMap[udpAddr.String()]; exist {
                uS.RUnlock()
                head := []byte(v)
                headLens := len(head)
                copy(buf2[0:], head[0:headLens])
                copy(buf2[headLens:], b[0:])
                sendData := buf2[0 : headLens+n]
                udpConn.WriteToUDP(sendData, uS.clientUdpAddr)
                //log.Printf("%s <<< head-> %v\n", uS.prefix, head)
                //log.Printf("%s <<< b-> %v\n", uS.prefix, b)
                //log.Printf("%s <<< data-> %v\n", uS.prefix, sendData)
            } else {
                fmt.Printf("%s WARN -> %s not in dstMap.\n", uS.prefix, udpAddr.String())
                uS.RUnlock()
                continue
            }
        }
    }
    uS.sigQuit <- true
    return
}

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

7922 次点击  ∙  1 赞  
加入收藏 微博
6 回复  |  直到 2021-11-15 10:04:44
jitongxi
jitongxi · #1 · 7年之前

"支持 lsp 劫持" 这个国内有人在卖, 好像几万吧,支持tcp udp的那种,主要还是windows编程吧.

benZ
benZ · #2 · 7年之前
jitongxijitongxi #1 回复

"支持 lsp 劫持" 这个国内有人在卖, 好像几万吧,支持tcp udp的那种,主要还是windows编程吧.

嗯嗯,公司这边lsp劫持用C++已实现且在正常使用,只是一时想到能不能用 go 来实现,写个小程序,加载配置文件来劫持某些exe.

xocoder
xocoder · #3 · 6年之前

楼主,这个SOCKS5服务端可以改成指定出口吗? 我可以免费提供LSP劫持的程序。

alphayan
alphayan · #4 · 6年之前
xocoderxocoder #3 回复

楼主,这个SOCKS5服务端可以改成指定出口吗? 我可以免费提供LSP劫持的程序。

可以

badboy
badboy · #5 · 4年之前

请问怎么实现服务端接收到请求 使用代理去访问?

如:本地使用proxycap连接到服务端 访问cip.cc ,服务端使用1.txt中的socks5代理去访问cip.cc

tablecell
tablecell · #6 · 3年之前

能支持https吗?

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传