using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
//Socks 5 RFC is available at http://www.faqs.org/rfcs/rfc1928.html.
namespace CSProxy
{
/// <summary>
/// Provides sock5 functionality to clients (Connect only). 协议规定客户端采用TCP方式联系代理服务器
/// </summary>
public class SocksProxy
{
private SocksProxy(){}
#region ErrorMessages
private static string[] errorMsgs= {
"Operation completed successfully.",//操作成功完成
"General SOCKS server failure.",//常规服务器失败
"Connection not allowed by ruleset.",//连接不被允许
"Network unreachable.",//网络不能到达
"Host unreachable.",//主机不能到达
"Connection refused.",//连接被拒绝
"TTL expired.",//TTL期满
"Command not supported.",//不支持的命令
"Address type not supported.",//不被支持的地址类型
"Unknown error."//未名的错误
};
#endregion
/// <summary>
/// 连接到socks5代理
/// </summary>
/// <param name="proxyAdress">代理服务期地址</param>
/// <param name="proxyPort">代理服务器端口</param>
/// <param name="destAddress">目标地址 Destination: 目的地,UDP命令时是本机的地址</param>
/// <param name="destPort">目标端口,UDP命令时是本机的UDP端口</param>
/// <param name="userName">用户名</param>
/// <param name="password">密码</param>
/// <returns>用于TCP连接的SOCKET</returns>
public static Socket ConnectToSocks5Proxy(string proxyAdress, ushort proxyPort, string destAddress, ushort destPort,string userName, string password)
{
IPAddress destIP = null;
IPAddress proxyIP = null;
byte[] request = new byte[257]; //请求
byte[] response = new byte[257];//应答
ushort nIndex;
try
{
proxyIP = IPAddress.Parse(proxyAdress);
}
catch(FormatException)
{
proxyIP = Dns.GetHostByAddress(proxyAdress).AddressList[0];
}
// 解析 destAddress (要求是类似 "212.116.65.112" 的string),否则是类似 "www.microsoft.com"的地址
try
{
destIP = IPAddress.Parse(destAddress);
}
catch(FormatException)
{
}
IPEndPoint proxyEndPoint = new IPEndPoint(proxyIP,proxyPort);
Socket s = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
s.Connect(proxyEndPoint);//客户端连到服务器后,然后就发送请求来协商版本和认证方法:
nIndex = 0;
request[nIndex++]=0x05; // V 5. [版本]
request[nIndex++]=0x02; // 2种验证方式 [方法的数目]
request[nIndex++]=0x00; // X'00' 不需要认证 [方法1]
request[nIndex++]=0x02; // X'02' 用户名/密码[方法2]
s.Send(request,nIndex,SocketFlags.None);
// 收到2个字节的应答,填充到response中,如果不是两个字节,则抛出异常
int nGot = s.Receive(response,2,SocketFlags.None);
if (nGot!=2) throw new ConnectionException("从 proxy server 返回错误的应答.");
// 当前定义的方法有:
// X'00' 不需要认证
// X'01' GSSAPI
// X'02' 用户名/密码
// X'03' -- X'7F' 由IANA分配
// X'80' -- X'FE' 为私人方法所保留的
// X'FF' 没有可以接受的方法
switch(response[1])
{
case 0xFF:
没有可以接受的方法(s);
break;
case 0x02:
用户名密码验证(s,userName,password);
break;
}
UDP命令(s,proxyIP);
// 连接成功
return s;
}
public static void 没有可以接受的方法(Socket s)
{
// 客户端没有一中验证方式能被服务器接受,则关闭该socket.
s.Close();
throw new ConnectionException("客户端没有一中验证方式能被代理服务器接受.");
}
public static bool 用户名密码验证(Socket s,string userName, string password)
{
byte[] request = new byte[257]; //请求
byte[] response = new byte[257];//应答
ushort nIndex;
byte[] rawBytes;
nIndex = 0;
request[nIndex++]=0x05; // Version 5. 不清楚为什么报文的首字节是0x01(按照惯例应当是0x05)
// 加入 user name
request[nIndex++]=(byte)userName.Length; //一个字节,放UserName的长度
rawBytes = Encoding.Default.GetBytes(userName);
rawBytes.CopyTo(request,nIndex); //将userName 加入
nIndex+=(ushort)rawBytes.Length;
// 加入 password
request[nIndex++]=(byte)password.Length; //一个字节,放PassWord的长度
rawBytes = Encoding.Default.GetBytes(password);
rawBytes.CopyTo(request,nIndex);
nIndex+=(ushort)rawBytes.Length;
// 发送 Username/Password 请求
s.Send(request,nIndex,SocketFlags.None);
// 收到2个字节的应答,填充到response中
int nGot = s.Receive(response,2,SocketFlags.None);
if (nGot!=2)
{
throw new ConnectionException("从 proxy server 返回错误的应答.");
}
if (response[1] != 0x00) //返回如下的报文字节序列映像为:0x01 | 验证结果标志-->0x00 验证通过,其余均表示有故障
{
throw new ConnectionException("错误的 Usernaem/Password.");
}
return true;
}
public static Socket socket;
public static string BndAddr;
public static ushort BndPort;
public static bool UDP命令(Socket s,IPAddress proxyIP )
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPEndPoint localIP = new IPEndPoint(IPAddress.Any,0);
socket.Bind(localIP);
EndPoint localEP =socket.LocalEndPoint;
IPAddress destIP =((IPEndPoint)localEP).Address;
ushort destPort =(ushort)((IPEndPoint)localEP).Port;
// 这个函数只实现了UDP 命令
//
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// CMD 命令
// o CONNECT X'01'
// o BIND X'02'
// o UDP ASSOCIATE X'03'
//
// ATYP 地址类型
// o IPv4 : X'01'
// o 网址 : X'03'
// o IPv6 : X'04'
//
// DST.ADDR 目标地址
// DST.PORT 目标端口
byte[] request = new byte[257]; //请求
byte[] response = new byte[257];//应答
ushort nIndex;
byte[] rawBytes;
nIndex = 0;
request[nIndex++]=0x05; // version 5.
request[nIndex++]=0x03; // command = UDP.
request[nIndex++]=0x00; // Reserve = 必须是 0x00
if (destIP != null)
{
switch(destIP.AddressFamily)//目的地址
{
case AddressFamily.InterNetwork://IPV4
request[nIndex++]=0x01;//第4个字节
rawBytes = destIP.GetAddressBytes();//肯定是4个字节的长度
rawBytes.CopyTo(request,nIndex);//第5678个字节
nIndex+=(ushort)rawBytes.Length;
break;
case AddressFamily.InterNetworkV6://IPV6
request[nIndex++]=0x04;
rawBytes = destIP.GetAddressBytes();
rawBytes.CopyTo(request,nIndex);
nIndex+=(ushort)rawBytes.Length;
break;
}
}
byte[] portBytes2 = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)destPort));
//portBytes2.CopyTo(request,nIndex);//这句为何不行?
for (int i=0;i<portBytes2.Length;i++)
{
request[nIndex++]=portBytes2[i];
}
// 发送连接请求
s.Send(request,nIndex,SocketFlags.None);
int len=s.Receive(response); //获得没有固定长度的应答
// 应答格式
//
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// o VER 版本: X'05'
// o REP
// o X'00' 成功
// o X'01' 普通的SOCKS服务器请求失败
// o X'02' 现有的规则不允许的连接
// o X'03' 网络不可达
// o X'04' 主机不可达
// o X'05' 连接被拒
// o X'06' TTL超时
// o X'07' 不支持的命令
// o X'08' 不支持的地址类型
// o X'09'到X'FF' 未定义
// o RSV 保留
// o ATYP 地址类型
// o IPv4 : X'01'
// o 网址 : X'03'
// o IPv6 : X'04'
// o BND.ADDR 代理服务器绑定的地址
// o BND.PORT 以网络字节顺序表示的代理服务器绑定的端口
//
if (response[1]!=0x00)
{
throw new ConnectionException(errorMsgs[response[1]]);
}
else
{
BndAddr= response[4].ToString()+"."+response[5].ToString()+"."+response[6].ToString()+"."+response[7].ToString();
ushort be=BitConverter.ToUInt16(response,8);
BndPort=(ushort)IPAddress.NetworkToHostOrder((short)be);
}
return true;
}
// 在传输UDP数据时,由于通过代理,所以需要按照一定的格式进行包装,在需要传送的数据之前添加一个报头,具体为:
// 保留2字节的0 | 是否数据报分段重组标志 | 地址类型 | 将要发到代理外的目标地址 | 远端目标主机的端口 | 需要通过代理传送出去的数据
// 这里的地址是最终接收此UDP数据的代理外的服务器地址
/// <summary>
/// 发送登陆信息
/// </summary>
public static byte[] sLogin(string UserID,string PassWord,int State)
{
jy.P2PBLL.Login login=new jy.P2PBLL.Login();
login.Flage =jy.P2PBLL.Messages.Login;
login.UserID=UserID;
login.PassWord=jy.P2PBLL.Crypt.Encrypt(PassWord);
login.State=State;
byte[] buffer = jy.P2PBLL.Trans.ToBytes("jy.P2PBLL.Login",login);
return buffer;
}
public static int 发送UDP数据(byte[] buffer)
{
IPAddress destIP=IPAddress.Parse("192.168.1.168");
ushort destPort=6680;
byte[] head= GetUdpDataHead(destIP,destPort);
buffer.CopyTo(head,10);//对于IPv4 是从10开始,IPv6 不是
int len=10+buffer.Length;
IPEndPoint IPEP=new IPEndPoint(IPAddress.Parse(BndAddr),BndPort);
socket.SendTo(head,len,SocketFlags.None,(EndPoint)IPEP);
return len;
}
public static byte[] GetUdpDataHead(IPAddress destIP,ushort destPort)
{
byte[] request = new byte[1034];
ushort nIndex;
byte[] rawBytes;
nIndex = 0;
request[nIndex++]=0x00; // 保留2字节的0
request[nIndex++]=0x00; // 保留2字节的0
request[nIndex++]=0x00; // 是否数据报分段重组标志
request[nIndex++]=0x01; // 地址类型
// 将要发到代理外的目标地址
// 远端目标主机的端口
if (destIP != null)
{
switch(destIP.AddressFamily)//目的地址
{
case AddressFamily.InterNetwork://IPV4
rawBytes = destIP.GetAddressBytes();//肯定是4个字节的长度
rawBytes.CopyTo(request,nIndex);//第5678个字节
nIndex+=(ushort)rawBytes.Length;
break;
case AddressFamily.InterNetworkV6://IPV6
rawBytes = destIP.GetAddressBytes();
rawBytes.CopyTo(request,nIndex);
nIndex+=(ushort)rawBytes.Length;
break;
}
}
byte[] portBytes2 = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)destPort));
for (int i=0;i<portBytes2.Length;i++)
{
request[nIndex++]=portBytes2[i];
}
return request;
}
}
}
#region connect 命令和 Bind 命令
// UdpCmd udpcmd=new UdpCmd();
// udpcmd.VER=0x05;
// udpcmd.CMD=0x03;
// udpcmd.RSV=0x00;
// udpcmd.ATYP=0x01;
// udpcmd.DstAddr=destIP.GetAddressBytes();
// udpcmd.DstPort=destPort;
//关于字节顺序
// 如果一个包,你自己打包发送,然后接收到后,自己进行处理,
// 如果你能确定发送端的操作系统和接收端的操作系统,使用同样的主机字节顺序,那么就不需要管字节的顺序
//
// 例如一个数据包中只存放一个int型数据,打包时使用byte[] intBytes = BitConverter.GetBytes(1);
// 此时得到的intBytes是按主机字节顺序(比如说是低字节顺序)排列的字节数组 [1 0 0 0]
// 将该包发出
// 接收端收到该包,使用 int getInt=BitConverter.ToInt32(data,0);[1 0 0 0]
//
// 但如果此时,接收端的主机字节顺序使用高字节顺序,则int getInt=BitConverter.ToInt32(data,0);[1 0 0 0] 得到的值是16777216
// 使用 big-edian 字节顺序,已规定网络为big-edian 字节顺序
/*
private static bool Connect命令(Socket s, string destAddress, ushort destPort,IPAddress destIP,IPAddress proxyIP )
{
// 这个函数只实现了connect 命令
//
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// CMD 命令
// o CONNECT X'01'
// o BIND X'02'
// o UDP ASSOCIATE X'03'
//
// ATYP 地址类型
// o IPv4 : X'01'
// o 网址 : X'03'
// o IPv6 : X'04'
//
// DST.ADDR 目标地址
// DST.PORT 目标端口
byte[] request = new byte[257]; //请求
byte[] response = new byte[257];//应答
ushort nIndex;
byte[] rawBytes;
nIndex = 0;
request[nIndex++]=0x05; // version 5.
request[nIndex++]=0x01; // command = connect.
request[nIndex++]=0x00; // Reserve = 必须是 0x00
if (destIP != null)
{
switch(destIP.AddressFamily)//目的地址
{
case AddressFamily.InterNetwork://IPV4
request[nIndex++]=0x01;
rawBytes = destIP.GetAddressBytes();
rawBytes.CopyTo(request,nIndex);
nIndex+=(ushort)rawBytes.Length;
break;
case AddressFamily.InterNetworkV6://IPV6
request[nIndex++]=0x04;
rawBytes = destIP.GetAddressBytes();
rawBytes.CopyTo(request,nIndex);
nIndex+=(ushort)rawBytes.Length;
break;
}
}
else
{
// 目标地址是域名
request[nIndex++]=0x03; // 地址是域名
request[nIndex++]=Convert.ToByte(destAddress.Length); // 该地址的长度
rawBytes = Encoding.Default.GetBytes(destAddress);
rawBytes.CopyTo(request,nIndex);
nIndex+=(ushort)rawBytes.Length;
}
// 使用 big-edian 字节顺序
byte[] portBytes = BitConverter.GetBytes(destPort);
for (int i=portBytes.Length-1;i>=0;i--)
{
request[nIndex++]=portBytes[i];
}
// 发送连接请求
s.Send(request,nIndex,SocketFlags.None);
int len=s.Receive(response); //获得没有固定长度的应答
// 应答格式
//
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// o VER 版本: X'05'
// o REP
// o X'00' 成功
// o X'01' 普通的SOCKS服务器请求失败
// o X'02' 现有的规则不允许的连接
// o X'03' 网络不可达
// o X'04' 主机不可达
// o X'05' 连接被拒
// o X'06' TTL超时
// o X'07' 不支持的命令
// o X'08' 不支持的地址类型
// o X'09'到X'FF' 未定义
// o RSV 保留
// o ATYP 地址类型
// o IPv4 : X'01'
// o 网址 : X'03'
// o IPv6 : X'04'
// o BND.ADDR 服务器绑定的地址
// o BND.PORT 以网络字节顺序表示的服务器绑定的端口
//
if (response[1]!=0x00)
{
throw new ConnectionException(errorMsgs[response[1]]);
return false;
}
return true;
}
private static bool Bind命令(Socket s, string destAddress, ushort destPort,IPAddress destIP,IPAddress proxyIP )
{
// 这个函数只实现了Bind命令
//
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// CMD 命令
// o CONNECT X'01'
// o BIND X'02'
// o UDP ASSOCIATE X'03'
//
// ATYP 地址类型
// o IPv4 : X'01'
// o 网址 : X'03'
// o IPv6 : X'04'
//
// DST.ADDR 目标地址
// DST.PORT 目标端口
byte[] request = new byte[257]; //请求
byte[] response = new byte[257];//应答
ushort nIndex;
byte[] rawBytes;
nIndex = 0;
request[nIndex++]=0x05; // version 5.
request[nIndex++]=0x02; // command = BIND.
request[nIndex++]=0x00; // Reserve = 必须是 0x00
if (destIP != null)
{
switch(destIP.AddressFamily)//目的地址
{
case AddressFamily.InterNetwork://IPV4
request[nIndex++]=0x01;
rawBytes = destIP.GetAddressBytes();
rawBytes.CopyTo(request,nIndex);
nIndex+=(ushort)rawBytes.Length;
break;
case AddressFamily.InterNetworkV6://IPV6
request[nIndex++]=0x04;
rawBytes = destIP.GetAddressBytes();
rawBytes.CopyTo(request,nIndex);
nIndex+=(ushort)rawBytes.Length;
break;
}
}
else
{
// 目标地址是域名
request[nIndex++]=0x03; // 地址是域名
request[nIndex++]=Convert.ToByte(destAddress.Length); // 该地址的长度
rawBytes = Encoding.Default.GetBytes(destAddress);
rawBytes.CopyTo(request,nIndex);
nIndex+=(ushort)rawBytes.Length;
}
// 使用 big-edian 字节顺序
byte[] portBytes = BitConverter.GetBytes(destPort);
for (int i=portBytes.Length-1;i>=0;i--)
{
request[nIndex++]=portBytes[i];
}
// 发送连接请求
s.Send(request,nIndex,SocketFlags.None);
int len=s.Receive(response); //获得没有固定长度的应答
// 应答格式
//
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// o VER 版本: X'05'
// o REP
// o X'00' 成功
// o X'01' 普通的SOCKS服务器请求失败
// o X'02' 现有的规则不允许的连接
// o X'03' 网络不可达
// o X'04' 主机不可达
// o X'05' 连接被拒
// o X'06' TTL超时
// o X'07' 不支持的命令
// o X'08' 不支持的地址类型
// o X'09'到X'FF' 未定义
// o RSV 保留
// o ATYP 地址类型
// o IPv4 : X'01'
// o 网址 : X'03'
// o IPv6 : X'04'
// o BND.ADDR 服务器绑定的地址
// o BND.PORT 以网络字节顺序表示的服务器绑定的端口
//
if (response[1]!=0x00)
{
throw new ConnectionException(errorMsgs[response[1]]);
return false;
}
return true;
}
*/
#endregion