adslproxy 代理池原理:

本文采用 redis 做代理IP的存储。存储代理IP前,先使用 adslproxy 做重新拨号和代理IP可用性校验,可用后再保存到 redis 里。在进行重新拨号前,先删除这个原来保存的代理 IP,然后再重新进行拨号操作。

然后使用的 adslproxy 搭建一个提取代理 IP 的工具,通过一个 web 接口给客户端使用的时候。客户端用的时候,直接读取这个 web 接口就可以使用代理 IP。


拨号服务器操作步骤如下:

本文采用阿斯云的拨号vps服务器做搭建平台。

  • 停止拨号的命令:pppoe-stop
  • 开始拨号的命令:pppoe-start

所以连起来重新拨号更换IP就是pppoe-stop;pppoe-start

1. 安装squid 和python3

yum -y install squid python3

2. 记录hosts

echo "127.0.0.1   $HOSTNAME" >> /etc/hosts

记录 hosts 的作用是,进行重新拨号更换IP的时候,可以更快的让squid 完成绑定网口。不然每次更换IP,squid都需要对主机名进行 DNS 查询,会比较慢。不然会在/var/log/squid/cache.log会出现如下信息:

2022/04/16 12:19:41 kid1| WARRNING: '4151624asy259' rDNS test failed: (0) No error.
2022/04/16 12:19:41 kid1| WARRNING: Could not determine this machines public hostname. Please configure one or set 'visible_hostname'.
2022/04/16 12:19:41 kid1| WARRNING: '4151624asy259' rDNS test failed: (0) No error.
2022/04/16 12:19:41 kid1| WARRNING: Could not determine this machines public hostname. Please configure one or set 'visible_hostname'.
2022/04/16 12:19:41 kid1| WARRNING: '4151624asy259' rDNS test failed: (0) No error.
2022/04/16 12:19:41 kid1| WARRNING: Could not determine this machines public hostname. Please configure one or set 'visible_hostname'.
2022/04/16 12:19:41 kid1| WARRNING: '4151624asy259' rDNS test failed: (0) No error.
2022/04/16 12:19:41 kid1| WARRNING: Could not determine this machines public hostname. Please configure one or set 'visible_hostname'.
2022/04/16 12:19:41 kid1| WARRNING: '4151624asy259' rDNS test failed: (0) No error.
2022/04/16 12:19:41 kid1| WARRNING: Could not determine this machines public hostname. Please configure one or set 'visible_hostname'.

又或者按照提示,在 squid 的配置文件里面设置 visible_hostname

3. 备份 squid.conf 文件

cp /etc/squid/squid.conf /etc/squid/squid.conf.bak

4. 设置代理的用户名和密码

squid 服务中设置代理的用户名密码可以用 htpasswd 命令来生成。该命令可以通过执行 apt-get/yum install http-tools来获取。
创建用户‘proxy_username’ 的命令如下:

htpasswd  -c /etc/squid/passwd proxy_username

输入相应的密码后,生成 文件 /etc/squid/passwd
squid 配置代理的用户名密码会使用basic_ncsa_auth 这个 lib 文件,一般会自带,因此需要正确的路径。 文件地址是 : /usr/lib64/squid/basic_ncsa_auth

5. 配置 squid 的文件

cat > /etc/squid/squid.conf << EOF
acl ::/0 src all

acl Safe_ports port 80
acl Safe_ports port 443
acl CONNECT method CONNECT

http_access allow all Safe_ports
http_access deny CONNECT !Safe_ports

auth_param basic program /usr/lib64/squid/basic_ncsa_auth /etc/squid/passwd
acl auth_user proxy_auth REQUIRED
http_access allow auth_user

http_port 3008

request_header_access Via deny all
request_header_access X-Forwarded-For deny all

EOF

6. 启动 squid 的开机默认启动

systemctl start squid
systemctl enable squid

7. 安装 adslproxy

pip3 install adslproxy

8. 修改源代码

在目前的v3.0.3版本的adslproxy代码中,发现两次地方有点问题,因此手动修改源代码:

(1) 修改 sender.py 的 extract_ip 方法

由于在 sender.py 中的 run() 里面如果subprocess.getstatusoutput(DIAL_BASH)执行重新拨号的命令提前结束,就会陷入无限拨号的问题。比如subprocess.getstatusoutput(DIAL_BASH)执行完了,拨号还没成功,还没获取到IP地址,需要等个10秒才获取到IP地址。此时提前进入self.extract_ip()获取IP,就会获取不到,ip 这个变量为空,进入 else 语句,马上重新执行 self.run() 重新拨号。。

所以需要修改 extract_ip 方法。修改如下文件:
/usr/local/lib/python3.6/site-packages/adslproxy/sender/sender.py

def extract_ip(self):
        """
        获取本机IP
        :param ifname: 网卡名称
        :return:
        """
        WAIT_TIME = 120
        while WAIT_TIME:
            (status, output) = subprocess.getstatusoutput('ifconfig')
            if not status == 0: return
            pattern = re.compile(DIAL_IFNAME + '.*?inet.*?(\d+\.\d+\.\d+\.\d+).*?netmask', re.S)
            result = re.search(pattern, output)
            if result:
                # 返回拨号后的 IP 地址
                return result.group(1)
            WAIT_TIME -= 1
            time.sleep(1)

(2) 修改 sender.py 的 run 方法

DIAL_CYCLE设置的时间是600秒进行一次重新拨号,实际是1200秒才进行重新拨号。如下代码所示:

    def loop(self):
        """
        循环拨号
        :return:
        """
        while True:
            logger.info('Starting dial...')
            self.run()
            time.sleep(DIAL_CYCLE)

    def run(self):
        """
        拨号主进程
        :return: None
        """
        logger.info('Dial started, remove proxy')
        try:
            self.remove_proxy()
        except RetryError:
            logger.error('Retried for max times, continue')
        # 拨号
        (status, output) = subprocess.getstatusoutput(DIAL_BASH)
        if not status == 0:
            logger.error('Dial failed')
        # 获取拨号 IP
        ip = self.extract_ip()
        if ip:
            logger.info(f'Get new IP {ip}')
            if PROXY_USERNAME and PROXY_PASSWORD:
                proxy = '{username}:{password}@{ip}:{port}'.format(username=PROXY_USERNAME,
                                                                   password=PROXY_PASSWORD,
                                                                   ip=ip, port=PROXY_PORT)
            else:
                proxy = '{ip}:{port}'.format(ip=ip, port=PROXY_PORT)
            time.sleep(10)
            if self.test_proxy(proxy):
                logger.info(f'Valid proxy {proxy}')
                # 将代理放入数据库
                self.set_proxy(proxy)
                time.sleep(DIAL_CYCLE)
            else:
                logger.error(f'Proxy invalid {proxy}')
        else:
            # 获取 IP 失败,重新拨号
            logger.error('Get IP failed, re-dialing')
            self.run()

在代码中看到的逻辑是 self.run() 里的self.set_proxy(proxy)写入完代理后,进行一次time.sleep(DIAL_CYCLE),然后 self.run()执行完后又time.sleep(DIAL_CYCLE)了一次。所以需要去掉if self.test_proxy(proxy):里的time.sleep(DIAL_CYCLE)

9. 创建运行脚本

新建 /data/adslproxy.sh 执行文件

#!/bin/bash
export REDIS_HOST='***'
export REDIS_PORT=6379
export REDIS_PASSWORD='***'
export DIAL_BASH='pppoe-stop;pppoe-start'
export DIAL_IFNAME='ppp0'
export DIAL_CYCLE=600
export CLIENT_NAME='adsl1'
export PROXY_PORT=3008
export PROXY_USERNAME='***'
export PROXY_PASSWORD='***'

nohup adslproxy send > /data/adslproxy.log 2>&1 &

设置的变量作用如下:

  • REDIS_HOST:保存到redis服务的 host 域名或者IP地址。
  • REDIS_PORT:保存到redis 服务端口号。
  • REDIS_PASSWORD:保存到redis的服务密码
  • DIAL_BASH:是拨号云服务器的重新拨号的命令,这里是pppoe-stop;pppoe-start
  • DIAL_IFNAME:是拨号云服务器上网卡名称,这里是ppp0网卡。
  • DIAL_CYCLE:是重新拨号的等待时间,单位为秒。
  • CLIENT_NAME:是设置到redis代理池里面的名字。
  • PROXY_PORT:用来测试代理可用性的代理服务端口号。
  • PROXY_USERNAME:设置代理的用户名。
  • PROXY_PASSWORD:设置代理的密码。

10. 启动 adslproxy,并设置开机启动

chmod u+x /data/adslproxy.sh
echo '/data/adslproxy.sh' >> /etc/rc.local
nohup adslproxy send >  /data/adslproxy.log 2>&1 &

拨号服务器端搭建完成。


服务端操作步骤如下:

服务端通过 Docker 的形式去搭建这个 web 服务。

  1. Dockerfile 内容如下:
FROM python:3.8.8-alpine3.13
RUN pip3 install --no-cache-dir adslproxy
CMD ["adslproxy" "serve"]
  1. 构建镜像:
docker build -t adslproxy:0.1 .
  1. 通过 docker-compose 文件启动这个镜像:
    docker-compose.yaml 文件内容如下:
version: '3'
services:
  adslproxy:
    image: 'adslproxy:0.1'
    container_name: adslproxy
    command: adslproxy serve
    ports:
      - "8425:8425"
    restart: always
    environment:
      REDIS_HOST: ${REDIS_HOST}
      REDIS_PASSWORD: ${REDIS_PASSWORD}
  1. 启动后,可以通过 http://IP:8425 这个接口去使用这个代理
    比如我的IP是192.168.1.240,就可以访问 http://192.168.1.240:8425/去使用这个接口工具
    image-1662969016506
    通过http://IP:8425/random 就可以随机获取一个代理,在代理里直接使用即可。