本文打算用java 实现一个socks5代理,写之前在网上搜索发现资料也比较少,决定做个总结
1.首先我们讲解什么是代理
用一幅形象的图解释什么事代理,我们的请求过来后 代理将请求转达到真正的网络,再将结果转发回来。我们的浏览器看起来就像和真正的网站之间进行通信一样,我们要做的就是对请求和响应的转发工作
2.要想接受浏览器的请求,必须先和应用程序之间进行握手连接,只有应用程序同意了,才能将数据包发给我们,我们才能将返回的结果发给应用程序,下面讲讲握手过程
socks5的文档,英语好的可以看看 ,下面我翻译一下握手过程
第一步,程序将发送给代理请求握手的信号
VER | NMETHODS |
METHODS |
1 | 1 | 1~255 |
ver 代表协议版本占用一个字节 这里肯定是 : 5
nmethods 代表下一个字段专用的字节数量 这里不确定
methods 代表客户端拥有的加密方式占用1~255个字节都有可能,比如说0 代表不加密 1代表加密 2代表 另一种加密等
代理收到后返回
VER | METHOD |
1 | 1 |
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 | 1 | 1 | 1 | variable | 2 |
cmd 字段,占用一个字节 1代表 想要tcp连接 3 代表想要udp连接
rsv 保留字,默认0
atyp 1 ip4
3 域名
4ip6
dst.addr 如果上一个字段是1 则这个字段是4位ip4地址
如果是3 则这个字段第一个字节代表域名长度,紧跟其后的是域名
如果是6 则这个字段16位ip6地址
dst.port 两个字节代表目的地端口
proxy返回
ver | rep | rsv | atyp | bnd.addr | bnd.port |
1 | 1 | 1 | 1 | 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);
}
}