基于go手动写个转发代理服务的代码实现
程序员文章站
2022-03-18 11:55:35
由于公司经常需要异地办公,在调试的时候需要用到内网环境,因此手动写了个代理转发服务器給兄弟们用:。
选型上,语言上就选择了go,简单清晰,转发协议选择了socks5。...
由于公司经常需要异地办公,在调试的时候需要用到内网环境,因此手动写了个代理转发服务器給兄弟们用:。
选型上,语言上就选择了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种认证方式:
- 0x00 不需要认证
- 0x01 gssapi
- 0x02 用户名、密码认证
- 0x03 - 0x7f由iana分配(保留)
- 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()
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。