java实现socks5

阅读更多
socks5的基础知识

关于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));
		InOutModel inOutModel = 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

你可能感兴趣的:(socks5)