关于socks5的定义]https://www.ietf.org/rfc/rfc1928.txt
[/b[b]
关于socks5的账号密码验证方式https://www.ietf.org/rfc/rfc1929.txt
socks5交互过程
1.socks5服务器开启端口
2.客户端链接到sockets服务器
3.客户端发送hello信息,结构如下
VER | NMETHODS | METHODS |
1字节 | 1字节 | 最高255字节 |
X05表示socks5,也可以是任意的identifier | 代表第三个字段的数据长度(byte数) | 客户端支持的验证方式,每种方法占用一个字节 |
其中X02表示账号密码验证,我们这里只需要实现该方法
ClientHelloInfo clientHelloInfo = new ClientHelloInfo(); clientHelloInfo.setVer(inputStream.read()); clientHelloInfo.setNmethods(inputStream.read()); if(clientHelloInfo.getNmethods() > 0) { clientHelloInfo.setMethods(read(clientHelloInfo.getNmethods())); }
4.服务端需要回复如下信息
VER | METHOD |
1字节 | 1字节 |
X05表示socks5 | 服务端挑选的验证方法 |
METHODS
o X'00' NO AUTHENTICATION REQUIRED
o X'01' GSSAPI
o X'02' USERNAME/PASSWORD
o X'03' to X'7F' IANA ASSIGNED
o X'80' to X'FE' RESERVED FOR PRIVATE METHODS
o X'FF' NO ACCEPTABLE METHODS
如果服务端最后挑选的是X'FF',则表示没有适配的验证方法,客户端与服务端的验证方案不匹配,这种情况意味着只能被关闭
public void writeDatas(ServerHelloInfo serverHelloInfo) throws IOException { outputStream.write(serverHelloInfo.getVer()); outputStream.write(serverHelloInfo.getMethod()); outputStream.flush(); }
5.客户端发送账号密码信息(如果不是账号密码验证则跳过这步)
VER | ULEN | UNAME | PLEN | PASSWD |
1字节,注意这里不是SOCKS的版本 | 1字节,指定用户名有多少字节 | 最大255字节 | 指定密码多少字节 | 最大255字节 |
public ClientUserPasswordInfo readDatas() throws IOException { ClientUserPasswordInfo clientUserPasswordInfo = new ClientUserPasswordInfo(); clientUserPasswordInfo.setVer(read()); clientUserPasswordInfo.setUlen(read()); clientUserPasswordInfo.setUname(new String(read(clientUserPasswordInfo.getUlen()))); clientUserPasswordInfo.setPlen(read()); clientUserPasswordInfo.setPassword(new String(read(clientUserPasswordInfo.getPlen()))); return clientUserPasswordInfo; }
6.服务端返回(如果不是账号密码验证则跳过这步)
VER | STATUS |
1字节,目前只支持1,因为目前SOCKS5的密码验证协议版本是1 | 0X00表示正确,其他表示密码错误,关闭连接 |
public void writeDatas(ServerUserPasswordInfo serverUserPasswordInfo) throws IOException { outputStream.write(serverUserPasswordInfo.getVer()); outputStream.write(serverUserPasswordInfo.getStatus()); outputStream.flush(); }
7.客户端请求代理目标
VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
1字节 | 1字节 | X'00' | 2字节 | 目标ip | 目标端口 2字节 |
- o VER协议版本:X'05'
- o CMD 三个命令模式
- o CONNECT模式X'01'
- o BIND模式 X'02'(开启端口监听,客户端需要连接到这个端口的流量会直接转发到目标IP端口)
- o UDP模式 X'03'
- o RSV保留
- o以下地址的ATYP地址类型
- o IP V4地址:X'01'
- o DOMAINNAME:X'03'(域名模式)
- o IP V6地址:X'04'
- o DST.ADDR所需的目标地址
- o DST.PORT网络八位字节中所需的目标端口
IP4模式,4字节
IP6模式,16字节
域名模式,地址第一个字段表示 域名长度(255最大)
地址端口的解析
端口2字节
package com.lsiding.nat.util; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.SocketTimeoutException; import com.lsiding.nat.core.InOutModel; public class UtilSocks5 { /** * byte数组转int类型的对象 4字节 * * @param bytes * @return */ public int byte2Int(Byte[] bytes) { return (bytes[0] & 0xff) << 24 | (bytes[1] & 0xff) << 16 | (bytes[2] & 0xff) << 8 | (bytes[3] & 0xff); } /** * 2个字节 转化成int * * @throws IOException */ public static final int byte2ToInteger(InOutModel> inOutModel) throws IOException { byte[] bs = inOutModel.read(2); return ((bs[0] & 0xff) << 8 | (bs[1] & 0xff)); } /** * 计算地址 一个byte刚好有0-255,但是当byte超过127时自动变成-128,比如说是129,那么当他超过127时自动从-128开始计算, * 也就是说129==--127 * * @return liyixing * @throws IOException * @throws SocketTimeoutException */ public static final String getAddress(int type, InOutModel> inOutModel) throws SocketTimeoutException, IOException { if (type == 3) { // 域名模式 int len = inOutModel.readLength(); byte[] bs = inOutModel.read(len); return new String(bs); } else if (type == 1) { // ip 4 byte[] bs = inOutModel.read(4); StringBuffer sb = new StringBuffer(); for (int i = 0; i < 4; i++) { if (i != 0) { sb.append("."); } int a = bs[i]; if (a < 0) { a = 256 + a; } sb.append(a); } return sb.toString(); } else if (type == 4) { // ip6 16字节 StringBuffer sb = new StringBuffer(); for (int i = 0; i < 8; i++) { if (i != 0) { sb.append(":"); } int a = byte2ToInteger(inOutModel); // a & 0xffff 显示4位16进制,a & 0xff 2位16进制 String x = String.format("%02x", new Integer(a & 0xffff)) .toUpperCase(); while (true) { if (x.length() < 4) { x = "0" + x; } else { break; } } sb.append(x); } return sb.toString(); } return null; } /** * int转byte数组 4字节 * * @param bytes * @return */ public byte[] intToByte(int num) { byte[] bytes = new byte[4]; bytes[0] = (byte) ((num >> 24) & 0xff); bytes[1] = (byte) ((num >> 16) & 0xff); bytes[2] = (byte) ((num >> 8) & 0xff); bytes[3] = (byte) (num & 0xff); return bytes; } /** * int 转2位byte * * @param i * @return liyixing */ public static byte[] intTo2ByteArray(int i) { byte[] result = new byte[2]; result[0] = (byte) ((i >> 8)); result[1] = (byte) (i & 0xff); return result; } /** * 地址转byte * * @param type * @param ip * @return liyixing */ public static final byte[] getAddressBytes(int type, String ip) { if (type == 3) { // 域名模式 byte[] bs = ip.getBytes(); int len = bs.length + 1; byte[] rs = new byte[len]; rs[0] = (byte) bs.length; for (int index = 0; index < bs.length; index++) { rs[index + 1] = bs[index]; } return rs; } else if (type == 1) { // ip 4 String[] ips = ip.split("\\."); byte[] rs = new byte[4]; for (int index = 0; index < 4; index++) { rs[index] = (byte) Integer.valueOf(ips[index]).intValue(); } return rs; } else if (type == 4) { // ip 4 String[] ips = ip.split(":"); byte[] rs = new byte[16]; for (int index = 0; index < 8; index++) { String ipOne = ips[index]; byte[] tm = intTo2ByteArray(Integer.valueOf(ipOne, 16)); rs[index * 2] = tm[0]; rs[index * 2 + 1] = tm[1]; } return rs; } return null; } public static void main(String[] args) throws IOException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( intTo2ByteArray(26381)); InOutModelinOutModel = new InOutModel ( byteArrayInputStream); System.out.println(byte2ToInteger(inOutModel)); byteArrayInputStream = new ByteArrayInputStream(getAddressBytes(3, "www.lsiding.com")); inOutModel = new InOutModel (byteArrayInputStream); System.out.println(getAddress(3, inOutModel)); byteArrayInputStream = new ByteArrayInputStream(getAddressBytes(1, "192.168.0.103")); inOutModel = new InOutModel (byteArrayInputStream); System.out.println(getAddress(1, inOutModel)); System.out.println("2001:0DB8:0000:0023:1008:0800:200C:0001"); byteArrayInputStream = new ByteArrayInputStream(getAddressBytes(4, "2001:0DB8:0000:0023:1008:0800:200C:0001")); inOutModel = new InOutModel (byteArrayInputStream); System.out.println(getAddress(4, inOutModel)); } }
public RequestInfo readDatas() throws IOException { RequestInfo requestInfo = new RequestInfo(); requestInfo.setVer(read()); requestInfo.setCmd(read()); requestInfo.setRsv(read()); requestInfo.setAtyp(read()); String ip = UtilSocks5.getAddress(requestInfo.getAtyp(), this); requestInfo.setDstAddr(ip); requestInfo.setPort(UtilSocks5.getProt(this)); return requestInfo; }
8.服务端回复
VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
1字节 | 1字节 | X'00' | 1字节 | Variable | 2字节 |
- o VER protocol version: X'05'
- o REP Reply 状态码:
- o X'00' succeeded
- o X'01' 一般SOCKS服务器故障
- o X'02' 不允许使用cmd 2
- o X'03' 网络无法访问
- o X'04' 主机无法访问
- o X'05' 连接被拒绝
- o X'06' TTL已过期
- o X'07' 命令不受支持
- o X'08' 不支持地址类型
- o X'09' X'09'到X'FF'未分配
- o RSV 保留字段 必须填0X00
- o ATYP 1ip4,3域名模式,4ip6
connect模式
BND.ADDR BND.PORT 与DST.ADDR和DST.PORT 相等
BIND模式,新开启的服务器的端口,ip
UDP 模式,不准备支持,详情查看https://www.ietf.org/rfc/rfc1928.txt