Web Proxy

RouterOS Web 代理为网络中的客户端获取网页内容。它按主机名、路径和方法过滤请求,缓存纯 HTTP 内容,通过父代理发送请求,并可作为普通代理或透明代理工作。

Web 代理

子菜单: /ip/proxy

Web 代理接受网络中客户端的网页请求,并代其从服务器获取内容。可用于:

  • 按客户端、主机名、路径或 HTTP 方法允许、拒绝或重定向请求。
  • 将纯 HTTP 内容缓存到路由器内存或磁盘。
  • 通过另一个代理(父代理)发送请求。

客户端可通过两种方式使用代理。普通代理客户端(例如配置了代理设置的浏览器)会将所有网页请求发送给代理,包括 HTTP 和 HTTPS。在透明模式下,防火墙规则将客户端的纯 HTTP 连接重定向到代理,客户端无需任何设置。

HTTPS 内容在客户端与服务器之间加密,因此代理仅进行中继。路由器可以按主机名、地址和端口允许或拒绝 HTTPS 连接,但无法查看请求路径、无法缓存内容,也无法在透明模式下接受 HTTPS 连接。按路径过滤和缓存仅适用于纯 HTTP。

Web 代理默认禁用,在 home 和 basic 设备模式下不可用。

配置普通代理

启用代理:

/ip/proxy/set enabled=yes

代理会在路由器的所有 IPv4 和 IPv6 地址上监听 TCP 端口 8080。要使用其他端口,请设置 port,例如 port=8080,3128。

在浏览器或操作系统的代理设置中,输入路由器地址(例如 192.168.88.1)和端口 8080,HTTP 与 HTTPS 均使用该设置。要在计算机上用 curl 测试代理:

curl -x http://192.168.88.1:8080 -I http://example.com/

仅允许自己的客户端

任何人都能访问的代理属于开放代理:他人可以通过它发送流量并隐藏来源。默认防火墙配置会丢弃不是来自 LAN 的到路由器连接,这同样保护了代理。如果防火墙接受来自其他网络的连接,请丢弃不是来自 LAN 的代理连接,IPv4 和 IPv6 均需处理:

/ip/firewall/filter/add chain=input protocol=tcp dst-port=8080 in-interface-list=!LAN action=drop comment="web proxy: LAN only"
/ipv6/firewall/filter/add chain=input protocol=tcp dst-port=8080 in-interface-list=!LAN action=drop comment="web proxy: LAN only"

请将这些规则移动到任何接受此类连接的规则之前。

代理可以向任意端口打开 HTTPS 隧道(CONNECT 方法)。要仅允许隧道连接到 HTTPS 端口 443:

/ip/proxy/access/add method=CONNECT dst-port=!443 action=deny

配置透明代理

在透明模式下,目标 NAT 规则将客户端的 HTTP 连接重定向到代理端口,客户端无需代理设置:

/ip/proxy/set enabled=yes
/ip/firewall/nat/add chain=dstnat protocol=tcp dst-port=80 in-interface-list=LAN action=redirect to-ports=8080 comment="transparent web proxy"

代理会连接请求中指定的主机(Host 头),而非客户端实际连接的地址。

透明模式仅适用于纯 HTTP。请勿将 HTTPS(端口 443)重定向到代理:代理不接受 TLS 连接,被重定向的 HTTPS 连接会失败。

同一个代理可以同时为普通客户端和透明客户端服务。

检查与故障排除

/ip/proxy/monitor 显示代理是否运行、打开的连接数,以及从缓存应答了多少请求(hits):

[admin@MikroTik] > /ip/proxy/monitor once
                 status: running
                 uptime: 35s
     client-connections: 0
     server-connections: 5
               requests: 313
                   hits: 69
             cache-used: 0KiB
         total-ram-used: 0KiB
  received-from-servers: 10090KiB
        sent-to-clients: 12126KiB
   hits-sent-to-clients: 1920KiB

/ip/proxy/connections/print 列出打开的连接:C 表示来自客户端的连接,S 表示到服务器的连接。

要记录每个请求,请为 web-proxy 主题添加日志规则:

/system/logging/add topics=web-proxy,!debug

每个请求都会记录客户端地址、方法、URL、访问列表的动作,以及代理是否从缓存应答:

web-proxy,account 192.168.88.10 GET http://example.com/  action=allow cache=MISS
web-proxy,account 192.168.88.10 CONNECT example.com:443  action=allow cache=MISS

如果不加 !debug,日志还会显示每个请求和响应的头信息。

常见问题:

  • 浏览器显示 ERROR: Forbidden 和 Access Denied:匹配到了 action=deny 的访问规则。/ip/proxy/access/print 的 hits 列显示是哪条规则。
  • 透明模式下 HTTPS 站点无法打开:透明模式仅适用于 HTTP,请只重定向端口 80。
  • ERROR: Gateway Timeout:代理无法访问服务器或父代理。页面会显示原因,例如 Operation timed out。
  • 缓存没有应答任何请求:大多数站点使用 HTTPS,代理无法缓存;许多 HTTP 响应也无法缓存。具体条件见缓存章节。

过滤请求

访问列表 /ip/proxy/access 决定代理处理哪些请求。路由器自上而下检查规则,第一条匹配的规则作出决定。不匹配任何规则的请求会被允许。只有当规则的所有匹配条件都匹配时规则才匹配;没有匹配条件的规则会匹配所有请求。

主机名和路径的匹配方式如下:

  • 值必须匹配整个主机名或路径。dst-host=example.com 不匹配 www.example.com。
  • * 匹配任意数量字符,? 匹配一个字符。dst-host=*.example.com 匹配 example.com 中的所有主机,但不匹配 example.com 本身。
  • 主机名匹配不区分大小写。
  • path 以 / 开头并包含查询字符串。path=*.mp3 匹配 /music/song.mp3,但不匹配 /music/song.mp3?id=1。
  • 以 : 开头的值是正则表达式,可匹配值的任意部分:dst-host=:ads 匹配所有包含 ads 的主机名。可使用 ^ 和 $ 匹配开头和结尾。
  • HTTPS 请求有主机名和端口,但没有路径,因此 path 规则永远不会匹配它们。

拒绝某个站点及其所有子域:

/ip/proxy/access
add dst-host=example.com action=deny
add dst-host=*.example.com action=deny

拒绝下载某些文件类型:

/ip/proxy/access
add path=*.exe action=deny
add path=*.zip action=deny

仅对某个网络拒绝某个站点:

/ip/proxy/access/add src-address=192.168.88.0/24 dst-host=*.example.net action=deny

仅允许部分站点,拒绝其他所有站点:

/ip/proxy/access
add dst-host=*.example.com action=allow
add action=deny

代理也可以将浏览器重定向到其他页面,而非直接拒绝请求。客户端会收到指向 action-data 中 URL 的 HTTP 307 重定向:

/ip/proxy/access/add dst-host=*.example.net action=redirect action-data=http://intranet.example.com/blocked.html

错误页面

代理会以错误页面(HTTP 403)应答被拒绝的请求,页面中列出 cache-administrator 作为联系人,其他错误也使用同一页面。要更改页面,请将默认模板写入路由器文件:

/ip/proxy/reset-html

这会创建 webproxy/error.html。编辑该文件或替换为您自己的 HTML 后,代理会立即使用新页面。模板可以使用以下变量:

  • $(status):HTTP 状态,例如 Forbidden。
  • $(url):请求的 URL。
  • $(error):原因,例如 Access Denied。
  • $(admin):cache-administrator 的值。
  • $(signature):包含时间和代理地址的一行内容。

/ip/proxy/reset-html 会恢复默认页面。

缓存

代理会存储获取到的纯 HTTP 内容,并在下一个客户端请求相同 URL 时直接发送,无需再次从服务器获取。代理运行时缓存默认开启,并位于内存中。

没有缓存副本时,代理从 Web 服务器获取文件并发送给用户;有缓存副本时,代理直接从缓存回答用户

当以下条件全部满足时,代理会存储响应:

  • 请求使用 GET 方法,且响应状态为 200(OK)或 301(Moved Permanently)。其他响应(例如 302 重定向和 404 错误)不会被存储。
  • 响应包含 Last-Modified 或 ETag 头,且不包含 Expires 头。
  • 响应未标记 Cache-Control: no-store 或 Cache-Control: private。
  • 响应大小不超过 max-cache-object-size(默认 2048 KiB)。
  • /ip/proxy/cache 中没有 deny 规则匹配该请求。

当存储的对象包含 Last-Modified 头且其 URL 不含查询字符串(?)时,代理会直接从缓存应答,不再询问服务器。对于其他已存储对象,代理会在每次请求时询问服务器对象是否已更改。

大多数网页内容通过 HTTPS 传输,其余内容中又有许多是为每个请求动态生成的,因此缓存能够应答的流量比例通常较小。

来自缓存的响应带有 X-Cache: HIT 头。路由器还会将其数据包标记为 DSCP 4(cache-hit-dscp),因此可以在防火墙和队列规则中匹配这些数据包。代理不会存储 Set-Cookie 头,因此客户端永远不会收到其他客户端的 Cookie。

缓存大小与存储

默认 max-cache-size=unlimited 时,内存缓存可以使用路由器的大部分空闲内存。在同时运行其他服务的路由器上,请以 KiB 为单位设置限制,例如 64 MiB:

/ip/proxy/set max-cache-size=65536

要将缓存保存在磁盘上,请设置 cache-on-disk=yes 并指定磁盘上的路径,例如 USB 磁盘 usb1:

/ip/proxy/set cache-on-disk=yes cache-path=usb1/web-proxy

缓存会以 web-proxy store 类型存储在 /file 中。设置缓存路径时,不存在的路径中的目录(包括所有中间目录)会自动创建:

[admin@MikroTik] >  /ip/proxy/set cache-path=usb1/cache-n-db/proxy/
[admin@MikroTik] >  /file/print
 # NAME                                              TYPE                             SIZE CREATION-TIME
 0 skins                                             directory                             2015-03-02 18:56:23
 1 sys-note.txt                                      .txt file                        23   2015-07-03 11:40:48
 2 usb1                                             disk                                  2015-07-03 11:35:05
 3 usb1/lost+found                                  directory                             2015-07-03 11:34:56
 4 usb1/cache-n-db                                  directory                             2015-07-03 11:41:54
 5 usb1/cache-n-db/proxy                            web-proxy store                       2015-07-03 11:42:09

如果只想使用代理进行过滤,请通过 max-cache-size=none 关闭缓存。

更改任何代理设置都会清空缓存。要手动清空,请运行 /ip/proxy/clear-cache。

将站点排除在缓存之外

缓存列表 /ip/proxy/cache 的工作方式与访问列表类似:第一条匹配规则作出决定,action=deny 会阻止代理存储响应。不匹配任何规则的请求会被缓存。例如,要始终不缓存某个站点的页面:

/ip/proxy/cache/add dst-host=*.example.com action=deny

查看缓存内容

/ip/proxy/cache-contents/print 列出已存储对象及其 URL 和大小。删除某个条目可让代理重新获取它。/ip/proxy/lookups 和 /ip/proxy/inserts 中的计数器显示缓存应答了多少请求以及存储了多少对象。

父代理

要通过另一个代理发送请求,请设置其地址和端口:

/ip/proxy/set parent-proxy=192.0.2.10 parent-proxy-port=3128

此后所有请求(HTTP 和 HTTPS)都会经过父代理。无法访问父代理时,客户端会收到错误页面:代理不会自行连接服务器。

直连列表 /ip/proxy/direct 指定不经过父代理而直接发往服务器的请求。第一条匹配规则作出决定:action=allow 将请求直接发送到服务器,action=deny 则通过父代理发送。不匹配任何规则的请求会经过父代理。例如,要直接访问某个域的服务器:

/ip/proxy/direct/add dst-host=*.example.com action=allow

技术细节

头信息

代理会在发往服务器的请求中添加以下头:

  • Via:代理标识,例如 1.1 ::ffff:192.168.88.1 (Mikrotik HttpProxy)。
  • X-Forwarded-For:客户端地址。路径上的每个代理都会将其客户端添加到列表中。
  • X-Proxy-ID:代理的标识符。

设置 anonymous=yes 后,代理不会添加上述任何头,服务器因此无法获知客户端地址。

来自缓存的响应带有 X-Cache: HIT 头。代理已向服务器验证过的存储对象会带有 X-Cache-Lookup: HIT。

HTTP 方法

访问、缓存和直连列表的 method 匹配器使用 RFC 9110 中定义的 HTTP 方法:

方法用途代理中的行为
GET获取页面或文件。代理仅缓存该方法的响应。
HEAD仅获取页面或文件的头信息。始终发送到服务器。
POST向服务器发送数据,例如表单。始终发送到服务器。
PUT、DELETE在服务器上存储或删除文件。发送到服务器。
OPTIONS、TRACE询问服务器支持哪些方法,或回显请求。发送到服务器。
CONNECT通过代理打开隧道,用于 HTTPS。代理中继隧道。规则可以匹配其主机名、地址和端口,但不能匹配路径。

FTP

普通代理客户端也可以请求 ftp:// URL。代理会从 FTP 服务器获取内容,并将目录列表作为 HTML 页面返回。

连接限制

max-client-connections 限制客户端连接数,默认 600。达到限制后,新请求会等待直到有连接空闲。

所有属性请参阅 /ip/proxy CLI 参考。