【字节跳动青训营】项目实战 - 制作一个简易代理服务器

简介: Go语言很强大,现在让我们来使用Go语言实现一个简易的代理服务器吧~直接上代码🤳


Go语言很强大,现在让我们来使用Go语言实现一个简易的代理服务器吧~直接上代码🤳


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: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 | NMETHODS | METHODS  |
  // +----+----------+----------+
  // | 1  |    1     | 1 to 255 |
  // +----+----------+----------+
  // VER: 协议版本,socks5为0x05
  // NMETHODS: 支持认证的方法数量
  // METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:
  // X’00’ NO AUTHENTICATION REQUIRED
  // X’02’ USERNAME/PASSWORD
  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)
  }
  // +----+--------+
  // |VER | METHOD |
  // +----+--------+
  // | 1  |   1    |
  // +----+--------+
  _, 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) {
  // +----+-----+-------+------+----------+----------+
  // |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
  // +----+-----+-------+------+----------+----------+
  // | 1  |  1  | X'00' |  1   | Variable |    2     |
  // +----+-----+-------+------+----------+----------+
  // VER 版本号,socks5的值为0x05
  // CMD 0x01表示CONNECT请求
  // RSV 保留字段,值为0x00
  // ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。
  //   0x01表示IPv4地址,DST.ADDR为4个字节
  //   0x03表示域名,DST.ADDR是一个可变长度的域名
  // DST.ADDR 一个可变长度的值
  // DST.PORT 目标端口,固定2个字节
  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)
  // +----+-----+-------+------+----------+----------+
  // |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
  // +----+-----+-------+------+----------+----------+
  // | 1  |  1  | X'00' |  1   | Variable |    2     |
  // +----+-----+-------+------+----------+----------+
  // VER socks版本,这里为0x05
  // REP Relay field,内容取值如下 X’00’ succeeded
  // RSV 保留字段
  // ATYPE 地址类型
  // BND.ADDR 服务绑定的地址
  // BND.PORT 服务绑定的端口DST.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
}
目录
相关文章
|
网络协议 C++ iOS开发
C++项目实战-UDP服务器
C++项目实战-UDP服务器
128 0
|
7月前
|
JSON 自然语言处理 网络协议
【字节跳动青训营】后端笔记整理-2 | Go实践记录:猜谜游戏,在线词典,Socks5代理服务器
猜数字游戏也算是入门一门编程语言必写的程序了。通过这个程序,我们可以熟悉Go语言中的输入输出、流程控制与随机函数的调用。
103 2
|
缓存 监控 Linux
C++项目实战-高并发服务器详析(二)
C++项目实战-高并发服务器详析(二)
83 0
|
Linux 调度 C++
C++项目实战-高并发服务器详析(一)
C++项目实战-高并发服务器详析(一)
236 0
|
存储 Cloud Native
带你读《企业级云原生白皮书项目实战》——7.2 无服务器
带你读《企业级云原生白皮书项目实战》——7.2 无服务器
|
IDE 物联网 测试技术
项目实战:Qt并发服务器通讯,受同一时刻最大线程数限制(笔者本本同一时刻600多)
项目实战:Qt并发服务器通讯,受同一时刻最大线程数限制(笔者本本同一时刻600多)
项目实战:Qt并发服务器通讯,受同一时刻最大线程数限制(笔者本本同一时刻600多)
|
数据安全/隐私保护 C++ Perl
07-rsync企业真实项目备份案例实战(需求收集--服务器配置---客户端配置---报警机制---数据校验---邮件告警)
2.需求描述 客户端需求: 1.客户端每天凌晨1点在服务器本地打包备份(系统配置文件、日志文件、其他目录、应用配置文件) 2.客户端备份的数据必须存放至以主机名IP地址当前时间命名的目录中,例如/backup/nfs_192.168.81.210_2020-05-26,其实更好的备份方式/backup/192.168.81.210/nfs_2020-05-26,一会采用后者,要求不只是备份文件,要求连上级目录一并拷过来
199 0
07-rsync企业真实项目备份案例实战(需求收集--服务器配置---客户端配置---报警机制---数据校验---邮件告警)
|
Web App开发 监控 测试技术
Github 开源项目(一)websocketd (实战:实时监控服务器内存信息)
官方地址:https://github.com/joewalnes/websocketd   websocketd是WebSocket守护进程,它负责处理WebSocket连接,启动您的程序来处理WebSockets,并在程序和Web浏览器之间传递消息。
2965 0