WPAD 通过让浏览器自动发现代理服务器,使代理服务器对用户来说是透明的,进而轻松访问互联网。WPAD 可以借助 DNS 服务器或 DHCP 服务器来查询代理自动配置(PAC)文件的位置。

引言

代理服务器大多被用来连接 INTERNET (国际互联网)和 INTRANET(企业内部网)。在多个局域网中需设置不同的代理服务器参数来使浏览器访问网络。在微软 Internet Explorer ( IE )5.0 以上版本中的功能中已经具备了自动切换代理服务器的功能。网络管理员需要事先部署代理服务器配置文件,然而用户方的设置却很简单。在这一功能中使用了被称为“WPAD”(Web Proxy Auto-Discovery protocol)的协议。

浏览器原本具有读入并解析代理服务器的配置文件,并将其配置信息设置到浏览器中的功能。配置文件是使用 Java Script 描述的,通常具有“.js”,“.jvs”,“.pac”(proxy auto-configuration)等扩展名的文件。

自动代理检测由系统确定,Web 代理服务器代表客户端发送请求。自动代理检测启用时,系统会尝试定位到代理发送请求后返回的代理配置文件位置。若查找到代理配置文件,那么在使用 Web 代理服务器实例得到代理信息、数据请求或响应信息时,在本地计算机上进行下载,编译并运行。

被部署的大多数配置文件的格式是 Proxy auto-config (PAC)。最初,PAC 是由 Netscape 在 1996 年为 Netscape Navigator 2.0 设计的。WPAD 协议草案由 Inktomi、微软、RealNetworks、Sun Microsystems 几个公司共同提出。WPAD 可支持所有主流浏览器,首次存在于 Internet Explorer 5.0 中。

WPAD 对代理服务器的透明处理使得管理员不再需要去每台客户机上设置代理服务器参数了。自动检测受动态主机配置协议 (DHCP) 和域名系统 (DNS) 的支持,浏览器通过 DHCP 和 DNS 的查询来搜索 PAC 文件的位置。

 

Proxy Auto-Config(PAC)文件

在开始介绍 WPAD 原理之前,有必要首先对代理自动配置(PAC)文件有一个概念上的认识。代理自动配置(PAC)文件定义了浏览器和其他用户代理如何自动选择适当的代理服务器来访问一个 URL。要使用 PAC,我们应当在一个网页服务器上发布一个 PAC 文件,并且通过在浏览器的代理链接设置页面输入这个 PAC 文件的 URL 或者通过使用 WPAD 协议告知用户代理去使用这个文件。

一个 PAC 文件是一个至少定义了一个 JavaScript 函数的文本文件。该函数 FindProxyForURL(url, host)有 2 个参数:

url 是一个对象的 URL;

host 是一个由这个 URL 所衍生的主机名。

按照惯例,这个文件名字一般是 proxy.pac。 WPAD 标准使用 wpad.dat。一个非常简单的 PAC 文件内容如下:

 function FindProxyForURL(url, host) {
if (url== 'http://www.baidu.com/') return 'DIRECT';
if (host== 'twitter.com') return 'SOCKS 127.0.0.10:7070';
if (dnsResolve(host) == '10.0.0.100') return 'PROXY 127.0.0.1:8086;DIRECT';
return 'DIRECT';
}

WPAD 的原理

DHCP 的自动检测

通过 DHCP 服务器,管理员可以集中指定全局 TCP/IP 参数和子网特定的 TCP/IP 参数,并可使用保留地址定义客户端的参数。如果客户端计算机在子网之间发生了移动,则在启动该计算机时,会自动重新配置 TCP/IP。

通过 DHCP 服务器部署 WPAD 的原理如下。首先确保 DHCP 服务器有效,然后为每个包含客户的子网建立了作用域。DHCP 服务器中的 252 选项通常被当作查询或注册用的指针,我们可以通过 252 项发现打印机、时间服务器、WPAD 主机以及其他的网络服务器。在 DHCP 服务器中添加一个用于查找 WPAD 主机的 252 项,252 项是一个字符串值,内容是部署在 WPAD 主机上 PAC 文件的 URL。为适当的作用域配置 252 项,就算只有一个作用域。具体部署 DHCP 服务器的操作,请见参考文献:ISA 防火墙之利用 DHCP 部署 WPAD。

因此 DHCP 客户机便可获得 PAC 文件的 URL,当客户机需要对浏览器或防火墙客户端进行自动配置时,就可以下载该 PAC 文件并得到代理服务器的地址。

图 1.DHCP 的自动检测示意图

  

在上图示意中,用户访问 laptop01.us.division.company.com 时,web 浏览器发送 DHCP INFORM 包于 DHCP 服务器来请求配置文件的位置,DHCP ACK 为 DHCP 服务器返回的数据包,其中 252 选项即为代理自动配置文件的位置。DHCP 报文格式如下图:

图 2.DHCP 报文格式

OP:消息操作代码,既可以是引导请求(1=BOOTREQUEST)也可以是引导答复(2=BOOTREPLY);

Htype:硬件地址类别,ethernet 为 1;

Hlen:硬件地址长度,ethernet 为 6;

Hops:若数据包需经过 router 传送,每站加 1,若在同一网内,为 0;

Transaction ID:事务 ID,是个随机数,用于客户和服务器之间匹配请求和相应消息;

Seconds:由用户指定的时间,指开始地址获取和更新进行后的时间;

Flags:从 0-15bits,最左一 bit 为 1 时表示 server 将以广播方式传送封包给 client,其余尚未使用;

Ciaddr:用户 IP 地址;

Yiaddr:服务器分配给客户的 IP 地址;

Siaddr:用于 bootstrap 过程中的 IP 地址;(服务器的 IP 地址)

Giaddr:转发代理(网关)IP 地址;

Chaddr:客户机的硬件地址;

Sname:可选 server 的名称,以 0x00 结尾;

File:启动文件名;

Options:厂商标识,可选的参数字段。此参数是 WPAD 实现过程中的关键参数,即相关了 PAC 文件的 URL。

DNS 的自动检测

DNS 是 TCP/IP 网络上的一组协议和服务,通过 DNS,用户可以使用分层的用户友好名称(主机)代替数字 IP 地址来搜索其他计算机。

用 DNS 来实现 WPAD,原理如下:

WPAD 工作原理是客户机向 DNS 服务器发起 WPAD+X 的查询。DNS 返回提供 WPAD 主机 IP 地址,客户机通过该 IP 的 80 端口去 WPAD 主机下载 WPAD.DAT(浏览器配置用文件)和 WSPAD.DAT(防火墙配置用文件)两个文件以实现自动配置。

客户机向 DNS 发起的 WPAD 查询的后缀是根据 WPAD 主机所处的环境决定的,如果客户机是在一个域环境下时,发起的查询便是一个“WPAD.所在域的域名”的标准域名查询,这种情况下配合 DNS 里添加 WPAD 主机的 A 或别名记录便可轻松在域环境中对 WPAD 主机的定位。

但是如果在工作组环境下时,客户机发起的查询可能是一个标准的域名查询(如果计算机名有加域名后缀)也可能只是个没有后缀的 WPAD 查询,这时就要通过创建 DNS 私有根域查询或是通过创建单标签域的方式进行 WPAD 主机查询。

从以上的原理分析,首先 WPAD 主机要在 80 端口提供 wpad.dat 和 wspad.dat,有了这两个文件,客户机上的浏览器或防火墙客户端才能实现自动配置。其次,DNS 服务器要创建相关记录,当客户机来查询时,将解析结果指向 WPAD 主机。

关于 DNS 的自动检测详细的分析,可以参考文献 工作组环境下 WPAD 部署的另类解决 。

图 3.DNS 的自动检测示意图

 

回页首

WPAD 的实现

通过 DNS 服务器部署 WPAD 在域的环境下比较适宜,但在工作组环境下就需要做一些改变。通过 DHCP 服务器部署 WPAD 还是更加方便,既不限制端口,又不受客户机计算机名影响,无论是工作组还是域都能很好地工作。

以下具体讨论了利用 DHCP 服务器来实现 WPAD 的 C++实例,并已通过 gcc 编译。此实例模拟了浏览器使用 DHCP 服务器实现 WPAD 的过程。同时希望能给有需要的开发人员一些关于如何实现 WPAD 的参考。

由上述通过 DHCP 服务器部署 WPAD 的原理,实现分为两大步骤,即向 DHCP 服务器发送 DHCP INFORM 包和接收 DHCP 服务器返回的 DHCP ACK 包,并解析。首先使用 Socket 实现广播(broadcast)的发送和接收来查找 WPAD 主机。广播是指在一个局域网中向所有的网上节点发送信息,是 UDP 协议的一种。在局域网中,广播 DHCP INFORM 包,当 DHCP 服务器接收到广播信息后,返回 DHCP ACK 包于客户机(本机),报文中的 252 选项即为查找的存储在 WPAD 主机上 PAC 文件的 URL。详细如下:

步骤一.查找 DHCP 服务器

  1. 在 WPAD 实现过程中,DHCP 报文为主要的数据结构,dhcp_send 代表 DHCP INFORM 包,dhcp_recv 代表 DHCP 服务器返回的 DHCP ACK 包。

    清单 1.DHCP 报文结构
     typedef struct dhcp_message_ {
    uint8_t op; /* message type */
    uint8_t hwtype; /* hardware address type */
    uint8_t hwlen; /* hardware address length */
    uint8_t hwopcount; /* should be zero in client message */
    uint32_t xid; /* transaction id */
    uint16_t secs; /* elapsed time in sec. from boot */
    uint16_t flags;
    uint32_t ciaddr; /* (previously allocated) client IP */
    uint32_t yiaddr; /* 'your' client IP address */
    uint32_t siaddr; /* should be zero in client's messages */
    uint32_t giaddr; /* should be zero in client's messages */
    uint8_t chaddr[DHCP_CHADDR_LEN]; /* client's hardware address */
    uint8_t servername[SERVERNAME_LEN]; /* server host name */
    uint8_t bootfile[BOOTFILE_LEN]; /* boot file name */
    uint32_t cookie;
    uint8_t options[DHCP_OPTION_LEN]; /* message options - cookie */
    }dhcp_message;
  2. 广播的实现首先创建 UDP 的 socket,将发送端口 socket 设置为广播类型,开启发送广播报文:
    清单 2.设置发送端口 socket 的属性为广播
      int bBroadcast=;  setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &bBroadcast, sizeof(bBroadcast)); 
  3. 接收端口绑定地址和这组广播的端口号(DHCP 统一使用两个 IANA 分配的端口作为 BOOTP : 服务器端 使用 67/udp, 客户端 使用 68/udp),接受广播信息:
    清单 3.绑定端口号
     struct sockaddr_in addr;
    bzero(&addr,sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(DHCP_CLIENT_PORT);//DHCP_CLIENT_PORT=68
    addr.sin_addr.s_addr = inet_addr("10.0.2.15");//local IP
    bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
  4. 将本机的 IP 地址和 MAC 地址等数据封装成 DHCP INFORM 报文,定义为 dhcp_send:
    清单 4.封装 DHCP 报文
     void make_message(const std::string& ip_addr, const std::string& mac_addr, dhcp_message **message)
    {
    dhcp_message* dhcp;
    dhcp = (dhcp_message*)xzalloc(sizeof (*dhcp));
    bzero(dhcp, sizeof(dhcp_message));
    dhcp->op = ;
    dhcp->hwtype = ; //ARPHRD_ETHER
    dhcp->hwlen = ; //MAC ADDR LENGTH
    dhcp->xid = ; //random
    dhcp->ciaddr = GetCiaddr(ip_addr);
    GetChaddr(mac_addr, dhcp->chaddr);
    dhcp->cookie = htonl(MAGIC_COOKIE);
    uint8_t *p = dhcp->options;
    //option 53
    *p++ = DHO_MESSAGETYPE;
    *p++ = ;
    *p++ = DHCP_INFORM;
    //option 55
    *p++ = DHO_PARAMETERREQUESTLIST;
    *p++ = ;
    *p++ = DHO_PACFILELOCATION;
    *p++ = DHO_END;
    *message = dhcp;
    }
  5. 广播 DHCP INFORM 报文
    清单 5.发送端口发送 DHCP INFORM 广播报文:
     struct sockaddr_in sin;
    bzero(&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr=inet_addr("255.255.255.255"); //Broadcast Address
    sin.sin_port = htons(DHCP_SERVER_PORT);//DHCP_server_PORT=67
    sendto(send_socket_fd, (uint8_t *)dhcp_send,sizeof(*dhcp_send) ,\
    (struct sockaddr *)&sin,sizeof(sin));

步骤二.接收 DHCP 服务器返回的 DHCP ACK 包,并解析,报文中的 252 项即为查找的存储在 WPAD 主机上 PAC 文件的 URL:

清单 6.将接收端口设置超时选项并接收消息:
 struct timeval tv_out;
tv_out.tv_sec = ;
tv_out.tv_usec = ;
setsockopt(recv_socket_fd,SOL_SOCKET,SO_RCVTIMEO,&tv_out, sizeof(tv_out));
recvfrom(recv_socket_fd, (uint8_t *)dhcp_recv, sizeof(*dhcp_recv), ,\
(struct sockaddr*)&addr,(socklen_t*)&addr_len);
清单 7.解析 DHCP ACK 包
 std::string AutoProxyDiscovery::dhcp_parser(uint8_t* options)
{
uint8_t* p = options;
int len;
std::string url;
while( (p-options) < DHCP_OPTION_LEN )
{
if( == *p )
{
++p;
len = *p++;
for( int i=; i<len; ++i,++p)
{
url.push_back(*p);
}
break;
}
else if( == *p )
{
break;
}
else
{
++p;
len = *p++;
p+=len;
}
}
return url;
}

步骤三.找到返回的 URL 下/data/wpad.dat 代理配置文件,在本地计算机上下载,编译并运行。即完成了 DHCP 的自动检测。

 

总结

之前的浏览器通常使用手动代理配置和代理自动配置等,而 WPAD 协议的出现更高级别地实现了自动化。使用 DHCP 来实现 WPAD 的思想较简单,是较好的解决方案。

WPAD 的原理及实现的更多相关文章

  1. 基于USB网卡适配器劫持DHCP Server嗅探Windows NTLM Hash密码

    catalogue . DHCP.WPAD工作过程 . python Responder . USB host/client adapter(USB Armory): 包含DHCP Server . ...

  2. Potato土豆win综合提权

    0x01 NBNS和WDAP NBNS: 在 Windows 系统中的另外一种名称就是 NetBIOS 名称,准确的说 NetBIOS 名称并非是一种名字系统,而是 Windows 操作系统网络的一个 ...

  3. DHCP的若干原理解释

    转自:http://blog.chinaunix.net/uid-22287947-id-1775641.html 搜罗了几种关于dhcp的原理和过程解释 DHCP(Dynamic Host Conf ...

  4. HTTP 代理原理及实现

    本文转载自 https://imququ.com/post/web-proxy.html HTTP 代理原理及实现(一) 文章目录 普通代理 隧道代理 Web 代理是一种存在于网络中间的实体,提供各式 ...

  5. HTTPS中间人攻击实践(原理·实践)

      前言 很早以前看过HTTPS的介绍,并了解过TLS的相关细节,也相信使用HTTPS是相对安全可靠的.直到前段时间在验证https代理通道连接时,搭建了MITM环境,才发现事实并不是我想的那样.由于 ...

  6. 在内网环境使用WPAD/PAC和JS攻击win10

    转:https://mp.weixin.qq.com/s/qoEZE8lBbFZikKzRTwgdsw 在内网环境使用WPAD/PAC和JS攻击win10 2018-03-01 wangrin 看雪学 ...

  7. 奇异值分解(SVD)原理与在降维中的应用

    奇异值分解(Singular Value Decomposition,以下简称SVD)是在机器学习领域广泛应用的算法,它不光可以用于降维算法中的特征分解,还可以用于推荐系统,以及自然语言处理等领域.是 ...

  8. node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理

    一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...

  9. 线性判别分析LDA原理总结

    在主成分分析(PCA)原理总结中,我们对降维算法PCA做了总结.这里我们就对另外一种经典的降维方法线性判别分析(Linear Discriminant Analysis, 以下简称LDA)做一个总结. ...

随机推荐

  1. Linux进程通信之System V共享内存

    前面已经介绍过了POSIX共享内存区,System V共享内存区在概念上类似POSIX共享内存区,POSIX共享内存区的使用是调用shm_open创建共享内存区后调用mmap进行内存区的映射,而Sys ...

  2. 写在新建博客的第一天 分类: fool_tree的笔记本 2014-11-08 17:57 144人阅读 评论(0) 收藏

    来CSDN开博客的目的有两个: 其一是因为CSDN的代码输出,看过一些博文,觉得这里的代码输出真的很漂亮: 其二则是因为,感觉自己印象笔记用久了之后,渐渐地几乎不再自己写些东西了,习惯了方便的剪藏插件 ...

  3. ExtractFileDir 与 ExtractFilePath 的差别

    ExtractFileDir 与 ExtractFilePath 的差别 ExtractFileDir 从文件名称中获取文件夹名(文件不在根文件夹下时取得的值后没有"/",在根文件 ...

  4. 自己写的Dapper通用数据访问层

    using Microsoft.Practices.EnterpriseLibrary.Data; using Microsoft.Practices.EnterpriseLibrary.Data.O ...

  5. 使用SqlAlchemy时如何方便的取得dict数据、dumps成Json

    使用Sqlalchemy可以方便的从数据库读取出python对象形式的数据(吐槽:说实话对象形式也没多方便,还不如我之前从关系型数据库直接读取出dict形式的数据用起来方便,具体参见我以前的文章htt ...

  6. 如何自定义echarts主题

    上一篇,选择echarts原有的主题样式,那么如何自定义自己的主题 与选择原有主题类似 1.echarts官网地址http://echarts.baidu.com/echarts2/doc  在工具中 ...

  7. Python之路【第十篇】:HTML -暂无等待更新

    Python之路[第十篇]:HTML -暂无等待更新

  8. 未能加载文件或程序集“Newtonsoft.Json, Version=4.5.0.0[已解决]

    在使用百度UEditor,不小心将Newtonsoft.Json,升级了,然后就报的一个错,说: 其他信息: 未能加载文件或程序集“Newtonsoft.Json, Version=4.5.0.0, ...

  9. (转)从内存管 理、内存泄漏、内存回收探讨C++内存管理

    http://www.cr173.com/html/18898_all.html 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟 ...

  10. DataTable和List集合互转

    /// <summary> /// 将集合转换成DataTable /// </summary> /// <param name="list"> ...