导航

C# Socks5 发送UDP数据包

Posted on 2006-03-28 20:22  zilong  阅读(3087)  评论(1编辑  收藏  举报

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