java实现一个socks5代理 <一>了解socks5协议

来源:互联网 发布:7z ubuntu 解压 编辑:程序博客网 时间:2024/05/17 03:13

本文打算用java 实现一个socks5代理,写之前在网上搜索发现资料也比较少,决定做个总结

1.首先我们讲解什么是代理


用一幅形象的图解释什么事代理,我们的请求过来后 代理将请求转达到真正的网络,再将结果转发回来。我们的浏览器看起来就像和真正的网站之间进行通信一样,我们要做的就是对请求和响应的转发工作


2.要想接受浏览器的请求,必须先和应用程序之间进行握手连接,只有应用程序同意了,才能将数据包发给我们,我们才能将返回的结果发给应用程序,下面讲讲握手过程

socks5的文档,英语好的可以看看 ,下面我翻译一下握手过程

第一步,程序将发送给代理请求握手的信号

VER

NMETHODS 
METHODS  
1 11~255

ver 代表协议版本占用一个字节 这里肯定是 : 5

nmethods 代表下一个字段专用的字节数量 这里不确定

methods 代表客户端拥有的加密方式占用1~255个字节都有可能,比如说0 代表不加密 1代表加密 2代表 另一种加密等


代理收到后返回

VERMETHOD11

method代表代理选择了一种握手方式

eg. 

client -->  5  2   0   2  代表着 版本5 有两种握手方式 加密的和不加密的供代理选择

proxy--> 5   0           代表着版本5  选择了不加密的协议方式

或者是

client--> 5 1 0 只有一种不加密的握手方式

proxy--> 5 0 选择了不加密的握手方式


上面的过程就叫做第一步握手连接,client 和proxy之间互相选择连接方式,如果client的所有握手方式proxy都不满足,则直接断开连接就好了



经过上一步的握手 client将发送如下信息到proxy

VER
CMD 
RSV  
ATYP 
DST.ADDR
DST.PORT
1 111variable2


cmd 字段,占用一个字节 1代表 想要tcp连接 3 代表想要udp连接

rsv 保留字,默认0

atyp 1 ip4

3 域名

4ip6

dst.addr  如果上一个字段是1 则这个字段是4位ip4地址

如果是3 则这个字段第一个字节代表域名长度,紧跟其后的是域名

如果是6 则这个字段16位ip6地址

dst.port 两个字节代表目的地端口


proxy返回

verreprsvatypbnd.addrbnd.port1111 2

rep 代表proxy告诉应用程序处理的情况,0代表处理成功,否则可以直接断开连接了

所以有可能是这样的情况

eg.

client-->  5 1  0 1  123 123 123 123 0 80  表示tcp连接 到123.123.123.123 的80 端口

proxy-->5  0  0 1   0 0 0 0 0 0  代表着已经连接上了,并且将atype置1代表后面的是ip和端口 ,由于大多客户端的实现都会忽略后面的ip和端口,我们直接全写0

这样握手就完成了。


然后我们已经拿到了程序想要访问的地址和端口,我们新建一个socket 自己连接到ip和端口,然后一边接受应用程序发的数据,转发给网站,一遍接受网站返回的数据,转发给应用程序,这样达到了让他们俩通信的目的


注意

这里解释一点,为什么java用两个byte表示端口,用四个byte表示ip地址,byte的范围是-128~127 而ip地址每一位的范围是0 ~255 之间从数量上一个byte刚好有256个数足够表示一位ip地址,但是当ip地址超过127时自动变成-128 ,也就是说当某位ip地址超过127 比如说是129,那么当他超过127时自动从-128开始计算,也就是说129==--127

我们拿到某位ip是负的时,我们只需要用256加上这位负的地址,就能求出真正的地址

两位地址表示端口,计算机上有65535个端口,一位byte只能表示256个数是远远不够的,这时当端口超过127 就从-128开始计算,当端口超过256 就向前进一位,从0开始计算,比如说端口时 1 60,并不是代表60端口,而是1*256+60=316 端口, 某端口时 1  -69 ,代表着1*256+(256-69)==443端口,如果你收到的端口时1  -69 则代表443端口(https用的端口),

公式就是 如果端口是正数   实际端口=256*前面一位+后面一位。如果端口是负的   实际端口=256*前面一位+(256+后面一位) ps(因为后面一位是负的,所以加上后面一位代表减去那个数的绝对值)。

这就是处理端口和ip的方法

附上根据一个bytebuffer 和长度 解析出ip/域名  和端口的方法

//解析地址public String getHost(ByteBuffer a,int len){if(len<8){return null;}StringBuffer sb=new StringBuffer();if(a.get(3)==3){//说明是网址地址int size=a.get(4); //网址长度for(int i=5;i<(5+size);i++){sb.append((char)a.get(i));}}else if(a.get(3)==1){//说明是ip地址for(int i=4;i<=7;i++){int A=a.get(i);if(A<0) A=256+A;sb.append(A);sb.append(".");}sb.deleteCharAt(sb.length()-1);}return sb.toString();}//解析端口public int getPort(ByteBuffer a,int len){if(len<4){return 0;}int port = a.get(len-1);int thod=a.get(len-2);if(port>0){return 256*thod+port;}else{return 256*thod +(256+port);}}