HTTP代理实现请求报文的拦截与篡改8补--自动设置与取消ADSL拔号连接的代理

返回目录 

  在本系列的 8--自动设置与取消代理 中,我们讲了自动设置与取消代理的功能,但有朋友反应那个只能设置默认的局域网代理,对于ADSL拔号连接的代理设置,就无能为力了,故而在这里我们就补讲一下ADSL拔号的代理设置方法,本来这个应该直接在8中进行修改的,但因为有源码的变动,所以单独开了一个补篇,以方便最新的源码下载。  

  我们先回忆一下上篇中讲到的局域网的代理是如何自动设置的。

 1 INTERNET_PER_CONN_OPTION_LIST list ;
 2 INTERNET_PER_CONN_OPTION options[5];  // 有几个选项需要设置这里就分配几个     
 3 list.pszConnection = NULL;   // 为NULL,表示默认的连接
 4 list.dwOptionCount = 5 ;  //  options的个数
 5 list.dwOptionError = 0 ; 
 6 list.pOptions = options;  // 指向options,就是选项的数组   
 7 
 8 options[0].dwOption = INTERNET_PER_CONN_FLAGS ;
 9 options[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY;
10 options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER ;
11 options[1].Value.pszValue = _T("http=127.0.0.1:8888;");   
12 options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS ;
13 options[2].Value.pszValue = _T("<-loopback>;");  
14 options[3].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL ;
15 options[3].Value.pszValue = NULL;  
16 options[4].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS;          
17 options[4].Value.dwValue = 0  ; 
18  
19 // 改变设置 
20 InternetSetOption(
21   NULL,
22   INTERNET_OPTION_PER_CONNECTION_OPTION,
23   &options,
24   sizeof(options)
25 );
26 
27  
28 // 通知所有的WINET实例,设置已经改变了,请立即更新代理设置     
29 InternetSetOption(
30   NULL,
31   INTERNET_OPTION_PROXY_SETTINGS_CHANGED,
32   NULL,
33   0 
34 );   

  注意一下,上面代码中红字标明的那行

list.pszConnection = NULL;  

  这里pszConnection的值为NULL,值为NULL就代表我们设置的是默认的局域网。那么如何设置ADSL拔号连接呢,很简单,把NULL改成某个ADSL拔号连接的名字就可以了            

  例如我们有二个如上图中所示的分别名为adsl1adsl2的拔号连接,那么如果我们要设置名为adsl1的那个拔号连接的代理只需要把上句的

list.pszConnection = NULL;

  改成

list.pszConnection = “adsl1”; 

  就可以了.

  同理如果要设置名为adsl2的拔号连接   

list.pszConnection = “adsl2”; 

  当然这些ADSL拔号连接的名字都是用户自己取的,我们是没办法事前知道他叫什么的,所以必须自动的去获取它,那么如何自动获取这些连接的名字呢 ?  这就要用到一个APIRasEnumEntries 了。 同样的,先看定义  

DWORD RasEnumEntries(
  _In_     LPCTSTR reserved,
  _In_     LPCTSTR lpszPhonebook,
  _Inout_  LPRASENTRYNAME lprasentryname,
  _Inout_  LPDWORD lpcb,
  _Out_    LPDWORD lpcEntries
);
The RasEnumEntries function lists all entry names in a remote access phone book. 
RasEnumEntries 函数列出所有远程访问电话本的名字   

Parameters  参数  
reserved [in]
Reserved; must be NULL.
保留以备后用,必须为NULL    
lpszPhonebook [in]
Pointer to a null-terminated string that specifies the full path and file name of a phone-book (PBK) file. If this parameter is NULL, the function uses the current default phone-book file. The default phone-book file is the one selected by the user in the User Preferencesproperty sheet of the Dial-Up Networking dialog box. 
以NULL结尾的字符串指针,这个字符串指定了一个电话本文件的全路径名。假如这个参数为NULL,这个函数就使用当前默认的电话本文件。默认的电话本是由用户在拔号网络对话框(Dial-Up Networking dialog box)的用户首选项(User Preferencesproperty)选项卡里选择的。 注:以上翻译不保证正确 :)   
If this parameter is NULL, the entries are enumerated from all the remote access phone-book files in the AllUsers profile and the user's profile. 
这句实在不知道怎么翻译,还是不翻了。反正这个参数为NULL,不影响大局 :)        
lprasentryname [in, out]
Pointer to a buffer that, on output, receives an array of RASENTRYNAME structures, one for each phone-book entry.
指向一个缓冲区的指针,当作为输出的时候,接受一个RASENTRYNAME 结构的数组,这个数组的每个元素代理一个电话条目  
On input, an application must set the dwSize member of the first RASENTRYNAME structure in the buffer to sizeof(RASENTRYNAME) in order to identify the version of the structure being passed.
作为输入的时候,必须使用sizeof(RASENTRYNAME) 设置RASENTRYNAME 结构的dwSize以便确定整个结构的大小。  
lpcb [in, out]
Pointer to a variable that, on input, contains the size, in bytes, of the buffer specified by lprasentryname.
Pointer to a variable that, on output, contains the size, in bytes, of the array of RASENTRYNAME structures required for the phone-book entries.
指向一个变量的指针,作为输入的时候,指向一个lprasentryname缓冲区的大小。作为输出的时候,表示这个电话条目所需要的RASENTRYNAME 结构的数组的大小。  (以上翻译基本上看不明白)  反正说白了,就是你传递一个数字进来,他也会返回去一个数字,这个数字,就是电话的条目数。也就是RASENTRYNAME这个数组有几项   
Windows Vista or later:  To determine the required buffer size, call RasEnumEntries with lprasentryname set to NULL. The variable pointed to by lpcb should be set to zero. The function will return the required buffer size in lpcb and an error code ofERROR_BUFFER_TOO_SMALL. 
在Windows Vista 或者更迟的版本,确定缓冲区大小的时候,要把lprasentryname设置成NULL,lpcb设置成0。这样这个函数将要将返回一个ERROR_BUFFER_TOO_SMALL错误,并将需要的缓冲区的大小放在lpcb里返回。   
lpcEntries [out]
Pointer to a variable that receives to the number of phone-book entries written to the buffer specified by lprasentryname. 
指向一个变量的指针,表示一个电话本条目的数量。   
Return value 返回值
If the function succeeds, the return value is ERROR_SUCCESS. 
假如成功返回ERROR_SUCCESS另外他后面还有一些错误情况下的返回码,这里就不详细列出翻译了,简单的说一下吧。
当lprasentryname的缓冲区不够大时,会返回ERROR_BUFFER_TOO_SMALL 错误,同时在lpcb里返回需要的缓冲区大小. 另外还有一个ERROR_INVALID_SIZE 用于处理NT系统之前的兼容性问题,2000以后的系统基本上不会出现这种问题,所以不说了 。 

  在上面的定义中出现了一个结构 RASENTRYNAME 

  我们也来看看他的定义   

typedef struct _RASENTRYNAME {
  DWORD dwSize;
  TCHAR szEntryName[RAS_MaxEntryName + 1];
#if (WINVER >= 0x500)
  DWORD dwFlags;
  TCHAR szPhonebookPath[MAX_PATH + 1];
#endif 
} RASENTRYNAME;
The RASENTRYNAME structure contains an entry name from a remote access phone book. The RasEnumEntries function returns an array of these structures. 
RASENTRYNAME 结构包含一个远程访问电话本的条目名。RasEnumEntries返回一个这个结构的数组 
Members 成员(只列出了用到前二个)
dwSize
Specifies the structure size, in bytes. Before using RASENTRYNAME in a function call, set this member to sizeof(RASENTRYNAME).
指定这个结构的大小.使用sizeof(RASENTRYNAME) 获得   
szEntryName
Specifies a string that contains the name of a remote access phone-book entry.
条目的名字  

  OKAY 定义已经全部列完了,下面直接给例子吧,因为这里涉及的结构比较少,所以直接使用C#的代码,大家顺便看看在C#中是怎么使用API的。 

当然在写例子前,我们还是要把刚才讲过的API以及它所涉及到的结构先用C#的方式定义下。 

  先看API  

[DllImport("rasapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern uint RasEnumEntries(IntPtr reserved, IntPtr lpszPhonebook, [In, Out] RASENTRYNAME[] lprasentryname, ref int lpcb, ref int lpcEntries);

注:DllImport属性的第一个参数就是指这个API所在的DLL的文件名。如果是自定义的DLL,需要放在和调用的程序同级的目录或者环境变量PATH列出的目录里,否则要使用全路径引用。    

  再看RASENTRYNAME结构体  

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
private struct RASENTRYNAME
{
    public int dwSize;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=0x101)]
    public string szEntryName;
    public int dwFlags;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=0x105)]
    public string szPhonebook;
}  

注:SructLayout,MarshalAs属性是指示程序如何在托管代码和非托管代码之间进行数据封送,C#和C++的数据布局是不同的,大部分情况下,CLR会自动转化,但有时候却又不行,所以必须象这样手动的指定一下。不过象这样手动的指定非常容易出错,所以一般情况下都不会这样去做,有专门的工具可以利用,微软官方也发布了一个:Signature Tool

 这里是他的下载地址,各位可以下下来,自己研究一下。http://download.microsoft.com/download/f/2/7/f279e71e-efb0-4155-873d-5554a0608523/CLRInsideOut2008_01.exe

  再定义几个用到的常量 

private const int ERROR_SUCCESS = 0x0;
private const int RASBASE = 600;
private const int ERROR_BUFFER_TOO_SMALL = RASBASE + 3;
private const int ERROR_INVALID_SIZE = RASBASE + 25; 

  结构定义好了,下面我们就可以来写例子,说是例子,其实就是新加入的代码,可以在附录的源码里找到,就是RASInfo.cs的 GetConnectionNames方法。 代码就不详细的讲了,我特意把注释写的很详细,看看注释就差不多可以明白了。

 1 internal static string[] GetConnectionNames()
 2 {
 3   // 计算RASENTRYNAME结构在非托管代码里的大小  
 4   int lpcb = Marshal.SizeOf(typeof(RASENTRYNAME));
 5   // 声明lpcEntries 
 6   int lpcEntries = 0;
 7   // 声明一个只有一个元素的RASENTRYNAME结构的数组。
 8   RASENTRYNAME[] lprasentryname = new RASENTRYNAME[1]; 
 9   // 设置第一个元素的结构体的大小(现在只有一个)   
10   lprasentryname[0].dwSize = lpcb;
11   // 执行RasEnumEntries,执行完成后,
12   // lpcEntries将会等于实际电话本里的条目数(如果使用前面的例子,那么这个条目数为2(adsl1和adsl2))
13   // lpcb将是实际需要的缓冲区大小  
14   // 如果lpcEntries>1  返回值(num3)将会是ERROR_BUFFER_TOO_SMALL
15   // 因为目前我们的缓冲区只能容纳一个RASENTRYNAME结构(RASENTRYNAME[]  lprasentryname = new RASENTRYNAME[1]; )
16   // 而如果电话本里有二个条目时,自然就会报缓冲区太小了。    
17   uint num3 = RasEnumEntries(IntPtr.Zero, IntPtr.Zero, lprasentryname, ref lpcb, ref lpcEntries);
18   // 如果不是返回成功,也不是返回缓冲区太小 
19   if ((num3 != ERROR_SUCCESS) && (ERROR_BUFFER_TOO_SMALL != num3))
20   {
21     // 那么lpcEntries=0;  
22     lpcEntries = 0;
23   }
24   // 声明存储连接名称的数组,+1的原因是还要加一个默认的DefaultLAN
25   // 如果机器上拔号连接有二个,则strArray的元素个数为2+1=3个。  
26   string[] strArray = new string[lpcEntries + 1];
27   // 存储第一个连接名 DefaultLAN 
28   strArray[0] = "DefaultLAN"; 
29   // 如果电话本里有拔号连接  
30   if (lpcEntries != 0)
31   {
32     // 根据电话本里实际的条目个数(lpcEntries)重新分配缓冲区大小  
33     lprasentryname = new RASENTRYNAME[lpcEntries];
34     // 为每个条目设置结构的大小  
35     for (int i = 0; i < lpcEntries; i++)
36     {
37       lprasentryname[i].dwSize = Marshal.SizeOf(typeof(RASENTRYNAME));
38     }
39     // 重新获取一次  
40     if (RasEnumEntries(IntPtr.Zero, IntPtr.Zero, lprasentryname, ref lpcb, ref lpcEntries) != 0)
41     {
42       return strArray;
43     }
44     // 将所有的拔号连接的名称追加到 存储连接名称的数组 刚才已经存了一个DefaultLAN了。 
45     for (int j = 0; j < lpcEntries; j++)
46     {
47       strArray[j + 1] = lprasentryname[j].szEntryName;
48     }
49   }
50   // 返回得到的所有连接名称  
51   return strArray;
52 }

  Okay,这一切都做好后,我们在外面,简单的

String[] connectionNames = RASInfo.GetConectionNames(); 

  就可以获取所有连接的名字了。

  获取所有连接的名字后,就可以循环使用WinINetProxy.SetToWinINET为每个连接设置代理选项了  

internal bool SetToWinINET(string connectionName) 

  还记得上节讲的这个方法吗,在WinINetProxy.cs里。 它的参数就是连接的名字。

  注:在新的代码里,我们为这个方法增加了一个参数,各位可以自行去看看哈    

  附件(源码+程序)   

posted @ 2013-03-15 19:57  乔伟2024  阅读(2089)  评论(2编辑  收藏  举报