基于go手动写个转发代理服务的代码实现

由于公司经常需要异地办公,在调试的时候需要用到内网环境,因此手动写了个代理转发服务器給兄弟们用:socks5proxy

选型上,语言上就选择了Go,简单清晰,转发协议选择了socks5。

SOCKS5协议介绍

SOCKS是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递,SOCKS是"SOCKetS"的缩写。 SOCKS5是SOCKS4的升级版,其主要多了鉴定、IPv6、UDP支持。

SOCKS5协议可以分为三个部分:

(1) 协议版本及认证方式
(2) 根据认证方式执行对应的认证
(3) 请求信息

(1)协议版本及认证方式

创建与SOCKS5服务器的TCP连接后客户端需要先发送请求来协议版本及认证方式,

VER NMETHODS METHODS
1 1 1-255
  • VER是SOCKS版本,这里应该是0x05;
  • NMETHODS是METHODS部分的长度;
  • METHODS是客户端支持的认证方式列表,每个方法占1字节。当前的定义是:
    • 0x00 不需要认证
    • 0x01 GSSAPI
    • 0x02 用户名、密码认证
    • 0x03 - 0x7F由IANA分配(保留)
    • 0x80 - 0xFE为私人方法保留
    • 0xFF 无可接受的方法

服务器回复客户端可用方法:

VER METHOD
1 1
  • VER是SOCKS版本,这里应该是0x05;
  • METHOD是服务端选中的方法。如果返回0xFF表示没有一个认证方法被选中,客户端需要关闭连接。

代码实现:

type ProtocolVersion struct {
  VER uint8
  NMETHODS uint8
  METHODS []uint8
}

func (s *ProtocolVersion) handshake(conn net.Conn) error {
  b := make([]byte, 255)
  n, err := conn.Read(b)
  if err != nil {
    log.Println(err)
    return err
  }
  s.VER = b[0] //ReadByte reads and returns a single byte,第一个参数为socks的版本号
  s.NMETHODS = b[1] //nmethods是记录methods的长度的。nmethods的长度是1个字节
  if n != int(2+s.NMETHODS) {
    return errors.New("协议错误, sNMETHODS不对")
  }
  s.METHODS = b[2:2+s.NMETHODS] //读取指定长度信息,读取正好len(buf)长度的字节。如果字节数不是指定长度,则返回错误信息和正确的字节数

  if s.VER != 5 {
    return errors.New("该协议不是socks5协议")
  }

  //服务器回应客户端消息:
  //第一个参数表示版本号为5,即socks5协议,
  // 第二个参数表示服务端选中的认证方法,0即无需密码访问, 2表示需要用户名和密码进行验证。
  resp :=[]byte{5, 0}
  conn.Write(resp)
  return nil
}

(2)根据认证方式执行对应的认证

SOCKS5协议提供5种认证方式:

  1. 0x00 不需要认证
  2. 0x01 GSSAPI
  3. 0x02 用户名、密码认证
  4. 0x03 - 0x7F由IANA分配(保留)
  5. 0x80 - 0xFE为私人方法保留

这里就主要介绍用户名、密码认证。 在客户端、服务端协商使用用户名密码认证后,客户端发出用户名密码:

鉴定协议版本 用户名长度 用户名 密码长度 密码
1 1 动态 1 动态

服务器鉴定后发出如下回应:

鉴定协议版本 鉴定状态
1 1

其中鉴定状态 0x00 表示成功,0x01 表示失败。

代码实现:

type Socks5Auth struct {
  VER uint8
  ULEN uint8
  UNAME string
  PLEN uint8
  PASSWD string
}

func (s *Socks5Auth) authenticate(conn net.Conn) error {
  b := make([]byte, 128)
  n, err := conn.Read(b)
  if err != nil{
    log.Println(err)
    return err
  }

  s.VER = b[0]
  if s.VER != 5 {
    return errors.New("该协议不是socks5协议")
  }

  s.ULEN = b[1]
  s.UNAME = string(b[2:2+s.ULEN])
  s.PLEN = b[2+s.ULEN+1]
  s.PASSWD = string(b[n-int(s.PLEN):n])
  log.Println(s.UNAME, s.PASSWD)
  if username != s.UNAME || passwd != s.PASSWD {
    return errors.New("账号密码错误")
  }

  /**
  回应客户端,响应客户端连接成功
  The server verifies the supplied UNAME and PASSWD, and sends the
  following response:
              +----+--------+
              |VER | STATUS |
              +----+--------+
              | 1 |  1  |
              +----+--------+
  A STATUS field of X'00' indicates success. If the server returns a
  `failure' (STATUS value other than X'00') status, it MUST close the
  connection.
  */
 resp := []byte{0x05, 0x00}
  conn.Write(resp) 

  return nil
}

但其实,现在大家都习惯自己采用加密流的方式进行加密,很少采用用户名密码的形式进行加密,后面章节会介绍一种对SOCKS的混淆加密方式。

(3)请求信息

认证结束后客户端就可以发送请求信息。如果认证方法有特殊封装要求,请求必须按照方法所定义的方式进行封装解密,其原始格式如下:

VER CMD RSV ATYP DST.ADDR DST.PORT
1 1 0x00 1 动态 2
  • VER是SOCKS版本,这里应该是0x05;
  • CMD是SOCK的命令码
    • 0x01表示CONNECT请求
    • 0x02表示BIND请求
    • 0x03表示UDP转发
  • RSV 0x00,保留
  • ATYP DST.ADDR类型
  • DST.ADDR 目的地址
    • 0x01 IPv4地址,DST.ADDR部分4字节长度
    • 0x03 域名,DST.ADDR部分第一个字节为域名长度,DST.ADDR剩余的内容为域名,没有\0结尾。
    • 0x04 IPv6地址,16个字节长度。
  • DST.PORT 网络字节序表示的目的端口

代码实现:

type Socks5Resolution struct {
  VER uint8
  CMD uint8
  RSV uint8
  ATYP uint8
  DSTADDR []byte
  DSTPORT uint16
  DSTDOMAIN string
  RAWADDR *net.TCPAddr
}

func (s *Socks5Resolution) lstRequest(conn net.Conn) error {
  b := make([]byte, 128)
  n, err := conn.Read(b)
  if err != nil || n < 7 {
    log.Println(err)
    return errors.New("请求协议错误")
  }
  s.VER = b[0]
  if s.VER != 5 {
    return errors.New("该协议不是socks5协议")
  }

  s.CMD = b[1]
  if s.CMD != 1 {
    return errors.New("客户端请求类型不为代理连接, 其他功能暂时不支持.")
  }
  s.RSV = b[2] //RSV保留字端,值长度为1个字节

  s.ATYP = b[3]

  switch s.ATYP {
  case 1:
    // IP V4 address: X'01'
    s.DSTADDR = b[4 : 4+net.IPv4len]
  case 3:
    // DOMAINNAME: X'03'
    s.DSTDOMAIN = string(b[5:n-2])
    ipAddr, err := net.ResolveIPAddr("ip", s.DSTDOMAIN)
 if err != nil {
  return err
 }
    s.DSTADDR = ipAddr.IP
  case 4:
    // IP V6 address: X'04'
    s.DSTADDR = b[4 : 4+net.IPv6len]
 default:
 return errors.New("IP地址错误")
  }

  s.DSTPORT = binary.BigEndian.Uint16(b[n-2:n])
  // DSTADDR全部换成IP地址,可以防止DNS污染和封杀
  s.RAWADDR = &net.TCPAddr{
 IP:  s.DSTADDR,
 Port: int(s.DSTPORT),
  }

  /**
  回应客户端,响应客户端连接成功
    +----+-----+-------+------+----------+----------+
    |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
    +----+-----+-------+------+----------+----------+
    | 1 | 1 | X'00' | 1  | Variable |  2   |
    +----+-----+-------+------+----------+----------+
  */
 resp := []byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
  conn.Write(resp) 

  return nil
}

(4)最后将信息进行转发即可

代码实现:

  wg := new(sync.WaitGroup)
  wg.Add(2)

  go func() {
 defer wg.Done()
 defer dstServer.Close()
    io.Copy(dstServer, client)
  }()

  go func() {
 defer wg.Done()
    defer client.Close()
    io.Copy(client, dstServer)
  }()

  wg.Wait()

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 基于go手动写个转发代理服务的代码实现

    由于公司经常需要异地办公,在调试的时候需要用到内网环境,因此手动写了个代理转发服务器給兄弟们用:socks5proxy. 选型上,语言上就选择了Go,简单清晰,转发协议选择了socks5. SOCKS5协议介绍 SOCKS是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递,SOCKS是"SOCKetS"的缩写. SOCKS5是SOCKS4的升级版,其主要多了鉴定.IPv6.UDP支持. SOCKS5协议可以分为三个部分: (1) 协议版本及认证方式 (2) 根据认证方式执

  • PHP队列原理及基于队列的写文件案例

    本文实例讲述了PHP队列原理及基于队列的写文件案例.分享给大家供大家参考,具体如下: 队列是一种线性表,按照先进先出的原则进行的: 入队: 出队: PHP实现队列:第一个元素作为队头,最后一个元素作为队尾 <?php /** * 队列就是这么简单 * * @link */ $array = array('PHP', 'JAVA'); array_push($array, 'PYTHON'); //入队列 array_shift($array); //出队列 什么是双端队列(或双向队列)Deque

  • 基于BootStrap环境写jQuery tabs插件

    一直想改版网站首页的图书展示部分,以前的展示是使用BootStrap的传统的collapse,网页篇幅占用大,也不够美观,操作也来说比较麻烦.于是有了自己利用Jquery来做一个图书展示的tabs的想法,之前也在网上找了半天,并不是很满意. 下面来看看我的实现过程: 首先是HTML部分,HTML部分按照结构简约,突出干货的思路来设计.选项卡使用了列表,在列表中插入了超链接,当然这里的超链接只是作为按钮使用的,当点击任何一个链接,就切换到对应的选项卡.选项卡的内容部分直接使用DIV,将图书的展示一

  • 基于SpringCloud手写一个简易版Sentinel

    Sentinel 是什么? 随着微服务的流行,服务和服务之间的稳定性变得越来越重要.Sentinel 以流量为切入点,从流量控制.熔断降级.系统负载保护等多个维度保护服务的稳定性. 不可否认的是,Sentinel功能丰富,并且在提供好用的dashboard提供配置,但是Sentinel在集成到项目中时需要引入多个依赖,并且需要阅读相关文档,以及dashboard中的相关配置才可以接入到项目中,这个过程还是较为复杂的. 如果我们的项目并不需要这么多的功能,只是需要当某个方法或者某个功能发生异常的时

  • 基于Python手写拼音识别

    目录 一.算法构造 1.简单介绍一下knn算法 2.Python实现KNN 二.准备数据 1.将图片转换成数组矩阵 三.处理数据:训练集与测试集 1.区分训练集和测试集 2.加载数据 3.建立训练数据 四.测试数据 一.算法构造 1.简单介绍一下knn算法 KNN算法,也叫K最近邻算法.功能是分类.算法逻辑非常简单,说直白点就是:先找到跟你最近的k个邻居(假设k=5),再看你的邻居给哪个类别投票(即邻居的标签),少数服从多数,得票最多的结果就是你的类别. 在这个算法中最关键的三点: k值 :选择

  • 基于Java手写一个好用的FTP操作工具类

    目录 前言 windows服务器搭建FTP服务 工具类方法 代码展示 使用示例 前言 网上百度了很多FTP的java 工具类,发现文章代码都比较久远,且代码臃肿,即使搜到了代码写的还可以的,封装的常用操作方法不全面,于是自己花了半天实现一个好用的工具类.最初想用java自带的FTPClient 的jar 去封装,后来和apache的jar工具包对比后,发现易用性远不如apache,于是决定采用apache的ftp的jar 封装ftp操作类. windows服务器搭建FTP服务 打开控制版面,图示

  • 如何写出优雅的JS 代码

    变量 使用有意义和可发音的变量名 // 不好的写法 const yyyymmdstr = moment().format("YYYY/MM/DD"); // 好的写法 const currentDate = moment().format("YYYY/MM/DD"); 对同一类型的变量使用相同的词汇 // 不好的写法 getUserInfo(); getClientData(); getCustomerRecord(); // 好的写法 getUser(); 使用可

  • 基于AngularJs + Bootstrap + AngularStrap相结合实现省市区联动代码

    Angular JS (Angular.JS) 是一组用来开发Web页面的框架.模板以及数据绑定和丰富UI组件.它支持整个开发进程,提供web应用的架构,无需进行手工DOM操作. AngularJs 就是一个函数库,算不上一个框架,源码2万2千多行,提供了前端MVC的开发方式,有双向绑定,指令等特性,这是具有革命性的.我是多么反感jQuery 用选择器 选择元素 ,绑定事件,进行一大堆DOM操作,一旦代码过多,非常不好维护,html结构改变,又要重写js代码,不过 jQuery  对 ajax的

  • 利用python代码写的12306订票代码

    本文实例讲述了python代码写的12306订票代码,分享给大家供大家参考. 具体实现方法如下: import datetime import json import re import sys import time import Image import PyV8 import requests import tools.email_helper as emailHelper reload(sys) sys.setdefaultencoding('utf-8') # @UndefinedVa

  • 基于jQuery实现仿51job城市选择功能实例代码

    前些文章用写过,省市县三级联动,但是感觉选择的时候不够直观,现在改进了下,效果如下图 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="City.aspx.cs" Inherits="System_Select_City" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transition

随机推荐