【青训营day1】如何使用go语言搭建一个socks5代理服务器| 青训营笔记

172 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天

[toc]

前言

根据教程的做法,是使用一步一步迭代进行完整的代理服务器搭建,其实思路跟验证模块差不多,先在框架下把一个模块验证完整后再添加新的模块。这里根据教程中的四次迭代进行笔记撰写。

第一代:简单复读机

package main

import (

    "bufio"

    "log"

    "net"

)

  


func main() {

    server, err := net.Listen("tcp", "127.0.0.1:1080")

    if err != nil {

        panic(err)

    }

    for {

        client, err := server.Accept()

        if err != nil {

            log.Printf("Accept failed %v", err)

            continue

        }

        go process(client)

    }

}

  


func process(conn net.Conn) {

    defer conn.Close()

    reader := bufio.NewReader(conn)

    for {

        b, err := reader.ReadByte()

        if err != nil {

            break

        }

        _, err = conn.Write([]byte{b})

        if err != nil {

            break

        }

    }

}

使用nc(netcat)进行简单tcp链接

nc 127.0.0.1 1080

此时,服务器返回的数据取决于客户端发送的数据

图片.png 因为在process函数中,服务端循环读取客户端发送的数据,并将其逐个输出到屏幕中。

然而实际上,在执行中,会将循环读取的操作合并,以减少资源消耗

第二代 鉴权

package main

  


import (

    "bufio"

    "fmt"

    "io"

    "log"

    "net"

)

  


const socks5Ver = 0x05

const cmdBind = 0x01

const atypIPV4 = 0x01

const atypeHOST = 0x03

const atypeIPV6 = 0x04

  


func main() {

    server, err := net.Listen("tcp", "127.0.0.1:1080")

    if err != nil {

        panic(err)

    }

    for {

        client, err := server.Accept()

        if err != nil {

            log.Printf("Accept failed %v", err)

            continue

        }

        go process(client)

    }

}

  


func process(conn net.Conn) {

    defer conn.Close()

    reader := bufio.NewReader(conn)

    err := auth(reader, conn)

    if err != nil {

        log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)

        return

    }

    log.Println("auth success")

}

  


func auth(reader *bufio.Reader, conn net.Conn) (err 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)

    }

    method := make([]byte, methodSize)

    _, err = io.ReadFull(reader, method)

    if err != nil {

        return fmt.Errorf("read method failed:%w", err)

    }

    log.Println("ver", ver, "method", method)

    _, err = conn.Write([]byte{socks5Ver, 0x00})

    if err != nil {

        return fmt.Errorf("write failed:%w", err)

    }

    return nil

}

这里使用的是无需鉴权,但是已经开始进行socks5协议的匹配。 这里一共读取了三个字段,分别是

1.版本号
2.支持认证方法的数量
3.认证方式

第三代 连接

package main

  


import (

    "bufio"

    "encoding/binary"

    "errors"

    "fmt"

    "io"

    "log"

    "net"

)

  


const socks5Ver = 0x05

const cmdBind = 0x01

const atypIPV4 = 0x01

const atypeHOST = 0x03

const atypeIPV6 = 0x04

  


func main() {

    server, err := net.Listen("tcp", "127.0.0.1:1080")

    if err != nil {

        panic(err)

    }

    for {

        client, err := server.Accept()

        if err != nil {

            log.Printf("Accept failed %v", err)

            continue

        }

        go process(client)

    }

}

  


func process(conn net.Conn) {

    defer conn.Close()

    reader := bufio.NewReader(conn)

    err := auth(reader, conn)

    if err != nil {

        log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)

        return

    }

    err = connect(reader, conn)

    if err != nil {

        log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)

        return

    }

}

  


func auth(reader *bufio.Reader, conn net.Conn) (err 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)

    }

    method := make([]byte, methodSize)

    _, err = io.ReadFull(reader, method)

    if err != nil {

        return fmt.Errorf("read method failed:%w", err)

    }

  


 

    _, err = conn.Write([]byte{socks5Ver, 0x00})

    if err != nil {

        return fmt.Errorf("write failed:%w", err)

    }

    return nil

}

  


func connect(reader *bufio.Reader, conn net.Conn) (err 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", ver)

    }

    addr := ""

    switch atyp {

    case atypIPV4:

        _, 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])

  


    log.Println("dial", addr, port)



    _, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})

    if err != nil {

        return fmt.Errorf("write failed: %w", err)

    }

    return nil

}

经过修改后,则此时可以正常识别到请求报文中的请求地址、端口等信息。

第四代 代理

package main

  


import (

    "bufio"

    "context"

    "encoding/binary"

    "errors"

    "fmt"

    "io"

    "log"

    "net"

)

  


const socks5Ver = 0x05

const cmdBind = 0x01

const atypIPV4 = 0x01

const atypeHOST = 0x03

const atypeIPV6 = 0x04

  


func main() {

    server, err := net.Listen("tcp", "127.0.0.1:10800")

    if err != nil {

        panic(err)

    }

    for {

        client, err := server.Accept()

        if err != nil {

            log.Printf("Accept failed %v", err)

            continue

        }

        go process(client)

    }

}

  


func process(conn net.Conn) {

    defer conn.Close()

    reader := bufio.NewReader(conn)

    err := auth(reader, conn)

    if err != nil {

        log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)

        return

    }

    err = connect(reader, conn)

    if err != nil {

        log.Printf("client %v auth failed:%v", conn.RemoteAddr(), err)

        return

    }

}

  


func auth(reader *bufio.Reader, conn net.Conn) (err 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)

    }

    method := make([]byte, methodSize)

    _, err = io.ReadFull(reader, method)

    if err != nil {

        return fmt.Errorf("read method failed:%w", err)

    }

  



    _, err = conn.Write([]byte{socks5Ver, 0x00})

    if err != nil {

        return fmt.Errorf("write failed:%w", err)

    }

    return nil

}

  


func connect(reader *bufio.Reader, conn net.Conn) (err 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", ver)

    }

    addr := ""

    switch atyp {

    case atypIPV4:

        _, 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()

    log.Println("dial", addr, port)


    _, 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

}

此时服务器可以正常代理,转发请求了。

在代码的第173行,使用了双向转发

图片.png

并且使用了context机制,在通信双方任意一方终止通讯时才会结束程序。