python 自动获取网关地址并开启代理

[toc]

前言

背景

有线网络能访问内网和公网,但是很多公网地址无法访问

无线网络能访问公网,不能访问内网,该路由器上有代理服务,限时开启;路由器 ip 前缀为 192.168.0.0/16,不固定

若仅连接无线网络,无法访问内网

需求

同时连接有线网络和无线网络

默认走有线网络

当路由器代理服务开启时,分流一部分访问到无线路由器代理服务

实现方案

使用脚本定时检测无线路由器代理是否开启,若开启,则自动设置 Windows 代理服务,并提供 pac 分流脚本

pac.py

import os
import signal
import subprocess
import sys
import threading
import winreg
from time import sleep
from socket import socket, AF_INET, SOCK_STREAM, SHUT_RDWR
from http.server import BaseHTTPRequestHandler, HTTPServer


class HttpHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path != '/':
            self.send_response(404)
            self.end_headers()
            return
        with lock:
            if server == '':
                response_str = "function FindProxyForURL(url, host) { return 'DIRECT'}"
            else:
                response_str = '''
function FindProxyForURL(url, host) {
    if (/^127\.0\.0\.1$|^localhost$|^10\./.test(host) ||
        /^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\./.test(host) ||
        /^192\.168\./.test(host)) {
        return 'DIRECT'
    } 
    
    if (/geely/.test(host) || /oss/.test(host)) {
        return 'DIRECT'
    }
'''
                response_str += "    return 'PROXY " + server + ":8080' \n}"
        # print(response_str)
        self.send_response(200)
        self.send_header('Content-type', 'text/plain; charset=utf-8')
        self.end_headers()
        self.wfile.write(response_str.encode())


def sys_proxy(enable):
    url = 'http://127.0.0.1:8080' if enable else ''
    reg_path = r'Software\Microsoft\Windows\CurrentVersion\Internet Settings'
    reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, reg_path, 0, winreg.KEY_WRITE)
    winreg.SetValueEx(reg_key, 'AutoConfigURL', 0, winreg.REG_SZ, url)
    if server != '':
        # 为了显示正在用的代理服务器
        winreg.SetValueEx(reg_key, 'ProxyServer', 0, winreg.REG_SZ, f'http://{server}:8080')
    if not enable:
        winreg.SetValueEx(reg_key, 'ProxyEnable', 0, winreg.REG_DWORD, 0)


def cleanup():
    global http
    global running
    running = False
    sys_proxy(False)
    os.kill(os.getpid(), 9)


def signal_handler(sig, frame):
    cleanup()


signal.signal(signal.SIGINT, signal_handler)
if sys.platform != "win32":
    signal.signal(signal.SIGHUP, signal_handler)

server = ''
lock = threading.Lock()
running = True

sys_proxy(False)
http = HTTPServer(('127.0.0.1', 8080), HttpHandler)
thread = threading.Thread(target=http.serve_forever)
thread.start()
ip = ''
while running:
    if ip == '':
        try:
            out = subprocess.check_output('ipconfig | findstr 默认网关 | findstr 192.168', shell=True)
            ip = out.decode('gbk').split(':')[1].strip()
        except subprocess.CalledProcessError:
            ip = ''
            sleep(1)
            continue
    sock = socket(AF_INET, SOCK_STREAM)
    try:
        sock.settimeout(0.1)
        sock.connect((ip, 8080))
        sock.shutdown(SHUT_RDWR)
    except TimeoutError:
        ip = ''
    sock.close()

    if server != ip and running:
        with lock:
            server = ip
            sys_proxy(server != '')

    sleep(1)

sys_proxy(False)

加到系统自启

不知道为什么,我这个脚本使用 pythonw 启动的话,本地的 8080 端口就不通了

所以就使用 vbs 来启动无窗口模式

start.vbs

Set WshShell = CreateObject("WScript.Shell")
WshShell.Run "python D:\documents\pycharm\pac\pac.py", 0, False

win + r 打开运行窗口,输入 shell:startup,将 start.vbs 放到打开的文件夹即可开机自启