使用squid搭建代理服务器

使用squid搭建代理服务器

squid是一款高效的http代理服务器程序,而且更经常被用来做缓存服务器。官网:http://www.squid-cache.org;还有一位大牛翻译的squid中文权威指南

一、安装squid

我的安装环境:Ubuntu 14.04.2 LTS

sudo apt-get install squid3

可以使用squid3 -v 检查安装好的squid

二、squid配置

squid默认配置文件为/etc/squid/squid.conf

2.1 基本配置

# http_port 设置监听端口,默认为3128
http_port 3128

# access_log 设置access日志,daemon表示在后台将日志写入/var/log/squid/access.log文件, 
# combined是一个预定义的logformat,也可以使用自定义的logformat
access_log daemon:/var/log/squid/access.log combined

# visible_hotname 设置代理服务器的主机名
# 默认取本机的hostname
visible_hostname funway.aliyun.proxy

注意,在作为正向代理的时候(squid默认配置),http_port 3128端口也可以处理https代理请求,因为作正向代理时squid并不需要参与ssl的加密解密,只需要帮忙从用户到网站的443端口建立tcp连接,然后无脑转发用户到网站之间的加密数据即可。只有当要将squid用作反向代理的时候,才需要用到squid的https_port配置,为squid设置证书。

2.2 访问控制

acl指令用来定义访问列表(Access List),用法:acl  aclname  acltype  argument

http_access指令用来定义接收还是拒绝来自acl的访问,用法:http_access  allow|deny  [!]aclname

squid.conf中默认的访问控制如下:

# ACLs all, manager, localhost, and to_localhost are predefined.
acl SSL_ports port 443
acl Safe_ports port 80		# http
acl Safe_ports port 21		# ftp
acl Safe_ports port 443		# https
acl Safe_ports port 70		# gopher
acl Safe_ports port 210		# wais
acl Safe_ports port 1025-65535  # unregistered ports
acl Safe_ports port 280		# http-mgmt
acl Safe_ports port 488		# gss-http
acl Safe_ports port 591		# filemaker
acl Safe_ports port 777		# multiling http
acl CONNECT method CONNECT

# 拒绝所有非Safe_ports的请求
http_access deny !Safe_ports
# 拒绝所有非SSL_prots的CONNECT请求
http_access deny CONNECT !SSL_ports

# Only allow cachemgr access from localhost
http_access allow localhost manager
http_access deny manager
# 允许来自本地的请求
http_access allow localhost

# 拒绝所有请求,最后兜底的规则
http_access deny all

在squid.conf的默认配置中,拒绝了所有的外部代理请求。这时候如果使用该代理,会返回错误页面:

屏幕快照 2016-04-02 下午2.21.54

上图中管理员邮箱由cache_mgr定义的,funway.aliyun.proxy由visible_hostname定义。

所以要想让其他主机使用squid代理,需要在最后一句http_access deny all前面添加我们自定义的规则。

# 定义访问列表AuthClients为183.26.163.xx网段内的所有主机
acl AuthClients src 183.26.163.0/24

# 允许AuthClients的代理请求
http_access allow AuthClients

然后在本地主机(在AuthClients网段中的)的浏览器上设置代理:(我用的Chrome +SwitchySharp插件)

屏幕快照 2016-04-02 下午1.26.55

通过代理访问http://www.hawu.me,打开开发者工具中的网络窗口,检查该请求的状态,可以看到Remote Address为我们设置的代理,在Response Headers里还有我定义的代理服务器名"funway.aliyun.proxy",表示这个请求是通过我们的代理服务器返回的。

屏幕快照 2016-04-02 下午1.27.31

注意:squid的http_access是按照配置文件中定义的顺序依次进行判断的!遇到第一个满足条件的http_access(allow或者deny)就立即返回!不再进行后续http_access判断。可以通过打开日志等级debug_options 28,5来查看http_access记录。

 

2.3 高级访问控制:用户验证

2.2介绍的是开启某个ip段的访问权限,除了限定ip,squid acl还可以做很多类型的访问控制,更详细的请参考官方文档。这里就打算介绍下如何使用squid自带的用户验证程序进行http basic authentication。

首先,我觉得需要先了解一下什么叫http authentication。这篇文章写得比较浅显易懂:http://blog.csdn.net/kiwi_coder/article/details/28677651

要让squid进行http authentication,需要用到auth_param配置:

# auth_param 设置代理的认证方式
# 用法:auth_param scheme parameter [setting]
# scheme表示认证机制,squid支持Basic,Digest,NTLM,Negotiate四种认证机制

# program 设置认证程序为ncsa_auth(squid自带不少认证程序以及其他拓展程序),程序所需密码文件passwd
auth_param basic program /usr/lib64/squid/ncsa_auth /etc/squid/passwd
# children 设置后台启动几个认证程序进程
auth_param basic children 3
# credentialsttl 设置认证失效时间,过期后需重新认证
auth_param basic credentialsttl 1 minute
# realm 设置认证时返回头里夹带的信息“wlecome to using my proxy”
auth_param basic realm welecome to using my proxy


# !!!上面只是设置了需要认证,并未将认证生效,还需设置acl与http_access

# 添加名为AuthUsers访问列表,表示通过认证的用户
acl AuthUsers proxy_auth REQUIRED
# 允许AuthUsers的代理请求
http_access allow AuthUsers

另外,我们需要手动生成密码文件/etc/squid/passwd,可以借组htpasswd程序(htpasswd是apache的一个命令行工具,用于生成管理 http 基本认证的密码文件。so,要使用htpasswd需要先安装apache)

htpasswd  -c  /etc/squid/passwd  funway

然后根据提示输入密码。就在passwd文件中生成了funway用户。

最后重启squid服务:service  squid  restart

浏览器首次通过代理进行访问的时候会弹出要求输入用户名密码的对话框:

屏幕快照 2016-04-02 下午3.07.02

 

ps:squid自带的拓展程序通常放在目录/usr/lib/squid下(64位的centos是/usr/lib64/squid),比如里面squid_db_auth程序可以用来做用户数据库方式的认证,ldap_auth可以用来连接ldap服务器进行用户验证。另外,除了使用squid自带的验证程序,我们还可以编写自己的验证程序让squid来调用,具体参考squid文档与google。

屏幕快照 2016-04-02 下午2.38.23

 

又ps:http_access的顺序会对结果有一些影响,最好要在上线前做好测试。比如说如果2.2中定义的AuthClients与2.3中定义的AuthUsers共存的话,如果http_access allow AuthClients在前,那么满足AutchClients的不需要用户认证就能访问,如果是http_access allow AuthUsers在前,那么就会先弹出用户认证。


另外,为了获得比basic auth更强的安全性,推荐使用digest auth。关于digest auth的原理,参考wiki:https://en.wikipedia.org/wiki/Digest_access_authentication(尤其里面的第四节 Example with explanation

以使用squid自带的验证程序digest_pw_auth为例,修改auth_param配置如下:

auth_param digest program /usr/lib64/squid/digest_pw_auth -c /etc/squid/digest.passwd
auth_param digest children 5 startup=1 idle=2
auth_param digest realm myproxy
auth_param digest nonce_garbage_interval 5 minutes
auth_param digest nonce_max_duration 30 minutes
auth_param digest nonce_max_count 100

然后为了生成digest.passwd密码文件,可以使用活雷锋apache提供的htdigest命令行工具,也可以使用如下脚本: $./gen_digest_pw.sh name pass 'myproxy' >> digest.passwd

#!/bin/sh
user="$1";
pass="$2";
realm="$3";
if [ -z "$1" -o -z "$2" -o -z "$3" ] ; then
echo "Usage: $0 user password 'realm'";
echo "注意: realm 必须与squid.conf中定义的一致";
exit 1
fi
# ha1 = md5(user:realm:password)
ha1=$(echo -n "$user:$realm:$pass"|md5sum |cut -f1 -d' ')
echo "$user:$realm:$ha1"

 

ps:2016.4.11 我后面发现digest_pw_auth有点问题,因为squid3.4+后的版本,对digest auth_param的返回值规则做了修改,而digest_pw_auth似乎没有更新=。=#。所以后来我自己写了一个digest辅助程序,并使用redis作为存储用户信息的数据库,这样读取会更快:https://github.com/funway/digest_redis_auth

又ps:2016.4.12 我发现我也是蛋疼的才想用digest auth,squid还是对basic auth支持的最好。想用acl max_user_ip来限制用户多地登陆,结果折腾了两天发现max_user_ip只支持basic auth,对于其他验证方法,根本没用!!!屏幕快照 2016-04-12 下午4.32.03

 

2.4 匿名代理

http头中有三个信息是用来给服务器鉴别用户的:remote_addr,http_via,http_x_forwarded_for。

用户不使用代理直接访问网站时,http头包含如下信息:

remote_addr = 用户真实ip

http_via = 不包含

http_x_forwarded_for = 不包含

用户使用普通代理访问时,对方服务器知道用户使用了代理,并且知道用户的真实ip。此时http头包含如下:

remote_addr = 代理服务器ip

http_via = 代理服务器主机名(squid的visible_hostname)

http_x_forwarded_for = 用户真实ip(如果用户使用了多层代理,这里应该是不包括最后一跳的整个ip链)

用户使用匿名代理访问时,对方服务器知道用户使用了代理,但不知道用户的真实ip。此时http头包含如下:

remote_addr = 代理服务器ip

http_via = 代理服务器主机名

http_x_forwarded_for = 代理服务器ip

用户使用高匿名代理访问时,对方服务器不知道用户使用了代理,也不知道用户真实ip。此时的http头包含如下:

remote_addr = 代理服务器ip

http_via = 不包含

http_x_forwarded_for = 不包含

squid默认是作为普通代理的,即开启via,并会写入http_forwarded_for。要想作为匿名代理,只需修改如下两个配置:

# 关闭via
via off
# 设置不修改http_forwarded_for
forwarded_for transparent

 

2.5 squid层级

在负责的网络环境中,我们可能要使用多个squid服务器构建一个squid集群,squid集群中可以有父子关系、兄弟关系。使用squid层级时,我们还可以定义squid A将某些流量转发给squid B,某些流量直接转发到目标服务器,或者所有流量都由squid B转发。这些主要用到squid的cache_peer、cache_peer_access、always_direct等配置指令。

屏幕快照 2016-04-06 下午1.59.10

详细配置可参考:

http://wiki.squid-cache.org/Features/CacheHierarchy

http://home.arcor.de/mailerstar/jeff/squid/chap10.html

 

2.6 其他配置

# debug_options, 设置cache.log的log level
# ALL表示全部模块,loglevel为1;28表示acl模块,loglevel为5,29表示用户认证模块,loglevel为9
debug_options ALL,1 28,5 29,9

# squid程序崩溃时的日志输出
coredump_dir /var/spool/squid

#
# 关于用户限制的配置
#
# maxconn, 设置一个ip的最大并发连接数
# 注意这个如果用户的连接是经过代理(很多环境下会存在透明代理),这个计数统计的是直连squid的ip。就是说如果你用家中两台电脑连接了squid,
# 对于squid而言你们的ip其实是一样的。因为你家(小区)的局域网出口都是一个ip。
acl aclname maxconn number
# max_user_ip, 当用户从不同ip通过验证登录时将会匹配这个值
# squid有一块“login cache”保存已登录用户的信息,好比[user:ip]这样的键值对
# [user:ip]在login cache块中的存活时间由authenticate_ip_ttl定义
acl aclname max_user_ip [-s] number
# authenticate_ip_ttl,定义在“login cache”中已登录用户的[user:ip]存活时间
authenticate_ip_ttl 3 minutes
# authenticate_ttl,定义用户登录信息在“login cache”中的存活时间,(跟authenticate_ip_ttl有啥区别=。=#)
authenticate_ttl 1 hour 
# authenticate_cache_garbage_interval, 定义“login cache”垃圾回收的时间间隔
authenticate_cache_garbage_interval 1 hour
# client_ip_max_connections,定义同一ip,在同一时间内所允许的最大连接数(感觉跟acl maxcoon有点类似)
client_ip_max_connections number

# cache_dir, 设置缓存目录,ufs表示文件系统格式,/var/spool/squid3表示缓存目录,大小100mb,16表示缓存目录下一级子目录数,256表示二级子目录数
# 默认没有开启缓存目录,缓存数据在内存中
cache_dir ufs /var/spool/squid 100 16 256

# 设置内存缓存大小(在内存中的cache,不是硬盘cache)
# PS:squid进程占用的内存大小除了与cache_mem有关,还有cache_dir的目录大小有关
cache_mem 256 MB

# refresh_pattern, 设置缓存的有效时间
refresh_pattern ^ftp:       1440    20% 10080
refresh_pattern ^gopher:    1440    0%  1440
refresh_pattern -i (/cgi-bin/|\?) 0 0%  0
refresh_pattern .       0   20% 4320

# 定义外部acl,指定squid发送某些信息到使用者自己编写的辅助程序(helper),辅助程序判断数据后返回是否符合acl。
external_acl_type name [options] FORMAT /path/to/helper [helper arguments]
acl aclname external class_name [arguments...]

 

三、墙外squid不稳定?被墙了!

其实我有两台云主机,一台墙内的阿里云ali,一台墙外的亚马逊aws。我先是在aws上搭的squid代理,但发现经常会出现连接被重置的情况,有时候能访问,有时候则直接显示ERR_CONNECTION_RESET。

屏幕快照 2016-04-02 下午4.50.10

开启squid的debug_options配置(debug_options  ALL,6),检查cache.log,只看到出错请求的后面有“errno 104”。这折腾了我一整天,怎么改配置,查错误都没解决,后来灵机一动,想到难道是因为GFW的原因?

然后我又用同样的配置在阿里云主机上搭了一个squid,发现果然连接正常了,完全不会CONNECTION RESET。看来被狗日的GFW墙了。

想了好久的原因,用wireshark抓包,惊奇的发现有到google.com的包(cache.log中有记录,我之前没注意到=。=#),想起来我用的是chrome浏览器,并且登录了我的gmail账号,后台会不时跟google同步,而这个流量也走了aws代理。所以当chrome后台通过aws代理请求google.com的时候,这个包被GFW监控到,然后GFW就直接把我的ip给墙了。

后面再请求weibo.com,此时我的ip已经被GFW记住了(大概会墙1、2分钟),GFW直接返回ERR_CONNECTION_RESET,并断开连接。

至于请求包,有可能会到达aws代理,也有可能不会到达而直接被GFW丢弃。因为我检查过squid日志,在google.com的记录后,我被墙期间的请求,有的会有记录,有的没有记录。但即使请求到了squid,也无法返回了,所有的后续日志都有一条“errno 104”,表示对端socket连接已经关闭。

这个问题的解决办法其实很简单,在浏览器的代理设置里忽略google.com即可:

屏幕快照 2016-04-02 下午6.54.56

 

四、squid + stunnel >> 科学上网

squid可以很方便的搭建http代理服务器,但从上面被墙的案例我们看到,单单使用墙外的squid代理是无法进行科学上网的。这时候就需要在墙内用户与墙外squid之间加一个stunnel,将我们发送给squid的请求进行加密。更详细的介绍请看下一篇文章http://www.hawu.me/operation/886

 

五、squid高级进阶

5.1 开发squid辅助程序

官方说明:http://wiki.squid-cache.org/Features/AddonHelpers

可以用任意语言开发squid辅助程序,只要能读写stdin与stdout。squid通过每次写一行数据到stdin,辅助程序解析该行数据,再从stdout返回一行结果给squid。

我自己尝试用python+redis写了两个squid的辅助程序,用来进行用户验证与限制。

5.2 内容篡改

squid有几个基本指令可以用来做内容篡改。

url_rewrite_program可以用来修改用户请求,将其转发到我们指定的url。这个指令有个简单粗暴的应用就是url过滤,禁止用户访问不受允许的网站,不想自己写url_rewrite_program辅助程序的话可以使用squidguard这个插件。

还有一个比较黑嘿嘿的应用就是篡改广告,举个栗子🌰,判断到用户请求的url是某个推广链接(就是浏览器右下角老弹出来的那种小广告)那么就可以将它rewrite到我们自己的推广链接。还有更隐蔽的做法,可以判断请求的是否js文件,如果是,后台下载过来然后篡改这个js文件加入我们想要执行的脚本,再放到我们自己的web服务器上通过url_rewrite_program返回给用户。简直bad、bad。


request_header_replacereply_header_replace、request_header_add、reply_header_add这几个指令可以用来篡改用户的http头。但对于http body就无能为力了。如果我们想篡改response body,嘿嘿,铛铛铛当!可以使用icap、ecap扩展,官方说明:http://wiki.squid-cache.org/SquidFaq/ContentAdaptation。感觉应该是icap好开发一些,但效率不如ecap。

我现在有一个点子似乎就得用到内容篡改,我想在返回给用户的cookie中加上代理自己的sessionid,模仿http会话的效果,这样做的原因有两个,因为使用http auth进行用户验证的话,每次请求头都要带上用户验证信息,效率低不说,有些垃圾浏览器还不会自动记住用户名密码,会每次都弹出窗口叫用户重新输入,这就有点不友好了。如果能在用户的cookie中加上代理自己的proxy-session-id,然后服务器将这个proxy-session-id放在内存或redis中,每次只要验证用户cookie就可以了,proxy-session-id超时了才重新进行http auth验证。这其实用reply_header_add应该可以实现,但我觉得reply_header_add实现起来好像很不方便,而且必须得用squid 4.0以上版本。以后有空了实践一下吧。

5.3 监控

5.3.1 access.log日志监控与分析

有很多access日志分析的软件,不过只有squidanalyzer是在持续更新的,http://squidanalyzer.darold.net/

屏幕快照 2016-04-26 下午3.20.22

5.3.2 snmp协议进行流量与资源监控

http://wiki.squid-cache.org/Features/Snmp

我用的zabbix,挺酷的。

 

10 thoughts on “使用squid搭建代理服务器

  1. jakk

    root@MustyConfused-VM:~# squid3 -z
    2018/12/24 18:24:00| http_port: missing Port: for
    FATAL: Bungled /etc/squid3/squid.conf line 1509: http_port for a list of generic options
    Squid Cache (Version 3.3.8): Terminated abnormally.
    CPU Usage: 0.010 seconds = 0.006 user + 0.004 sys
    Maximum Resident Size: 21504 KB
    Page faults with physical i/o: 0
    root@MustyConfused-VM:~# service squid3 status
    squid3 stop/waiting
    root@MustyConfused-VM:~# service squid3 start
    squid3 start/running, process 2320
    root@MustyConfused-VM:~# service squid3 status
    squid3 stop/waiting
    root@MustyConfused-VM:~# netstat -lntp
    Active Internet connections (only servers)
    Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
    tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 688/sshd
    tcp6 0 0 :::22 :::* LISTEN 688/sshd

    您好,能否帮我看一下为什么squid3无法运行,已经多次service squid3 restart了,但查看状态时,却显示没有打开。还有查看端口状态,发现3128也没有被使用。
    查看squid3 -z初始化状态时,发现port有错误,我是这样设置的: http_port 0.0.0.0:3128
    这样设置不可以吗?
    盼回,感谢!

    1. 哈呜 Post author

      你试试不加0.0.0.0,直接http_port 3128?
      或者你看看squid崩溃时候的日志,应该是在/var/spool/squid或者/var/log/squid或者可能写到系统的错误日志里面了,找找有没更详细的记录。

  2. denel

    您好,一条 ip 如何绑定多个端口?请教一下,感谢
    例如:35.246.62.45:2055:user:pw
    35.246.62.45:2056:
    35.246.62.45:2057:

  3. fei

    你好,当我在一个浏览器上通过了代理验证后,切换浏览器,还需要再次验证。之所以纠结这个问题,是因为,当我在手机上设置代理时,除了浏览器能弹出验证框,其他的软件,类似于微博,均无法验证登陆,而呈现断网状态。我相信,这个问题存在的原因是,软件由于没办法弹出验证框,因此,即便ip没有变化,也因为无法输入用户名,而导致验证失败。是否有一个好的解决办法,当我在一个地方,使用此ip通过验证登陆,之后此ip就无需再使用验证登陆。类似于,将ip设置为白名单,优先于验证登陆之间获得许可。在此问题上,已经纠结了许久,没有更好的办法。

    1. 哈呜 Post author

      我觉得你的想法是可行的,但是我没试过。
      我尝试搜索了一下,大概可以找到以下资料:
      1. 自定义用户验证程序从 auth_param key_extras 获取用户ip。
      参考 https://stackoverflow.com/questions/26601943/how-to-configure-squid-to-pass-client-ip-to-authenticate-program?rq=1
      然后将该 ip 储存在文件或数据库中。
      2. 自定义 acl helper。该 helper 程序将用户 ip(%SRC)与上面存储的 ip 进行比对。存在就返回 OK,否则返回 ERR。
      参考 githubsquid manualsquid develop
      3. 注意定义 http_access 的时候,acl helper 要在放在 acl auth_param 前面。这样已验证过的 ip 就不会再次需要用户登录验证了。

Leave a Reply

Your email address will not be published. Required fields are marked *

TOC