VicroCode
让代码创造价值
VicroCode是一个轻量级代码在线发布、交易平台,开箱即用,免部署、免服务器、免备案,支持接入智能体、通用管理系统、游戏等多种项目
稍候片刻,您可先学习AI编程手册
正在加载中...

AI MARKET GUIDE

我用Python给NAS写了个IPv6代理,结果只留了三个端口

看到有人用Go写了自动给容器开v6和HTTPS的工具,我也想试试。本来想全自动扫描端口,最后发现手动指定三个常用端口反而更稳。记录一下用VicroCode的Python环境验证socket代理逻辑、SQLite记日志、以及为什么没做端口扫描的整个过程。

起因:容器只听IPv4,公网v6进不来

家里几个Docker容器一直有个老毛病:应用绑在`0.0.0.0`上,公网IPv6根本连不进来。之前想过装Nginx做反向代理,但又觉得为了两三个自用服务跑一套反代有点重。

周末刷到一个帖子,作者用Go写了个叫nasconn+的工具,专门干两件事:给IPv4端口自动开一份IPv6代理,顺便嗅探HTTP流量自动在`端口+1`上挂HTTPS。听着挺对路,但我不想直接往NAS上扔二进制文件,就想着能不能用Python自己写个精简版,只处理我常用的几个端口。

第一版:socket转发+端口硬编码

最开始的想法很简单:监听`::`(IPv6全零地址),收到连接后往`127.0.0.1:目标端口`转发。用Python在线运行环境直接测了一下核心逻辑,大概30行代码就能跑通一个基础的TCP代理。

import socket
import threading

def forward(source, destination):
    try:
        while True:
            data = source.recv(4096)
            if not data:
                break
            destination.sendall(data)
    finally:
        source.close()
        destination.close()

def handle_client(client_sock, target_port):
    try:
        target = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        target.connect(('127.0.0.1', target_port))

        threading.Thread(target=forward, args=(client_sock, target)).start()
        threading.Thread(target=forward, args=(target, client_sock)).start()
    except Exception as e:
        print(f"连接失败: {e}")
        client_sock.close()

def start_proxy(listen_port, target_port):
    server = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('::', listen_port))
    server.listen(5)
    print(f"代理启动: [::]:{listen_port} -> 127.0.0.1:{target_port}")

    while True:
        client, addr = server.accept()
        threading.Thread(target=handle_client, args=(client, target_port)).start()

这版代码在本地测能通,但直接扔到NAS上跑会遇到几个实际问题:进程挂了怎么办?多个端口要启多少个Python脚本?

日志往哪存?

记日志:SQLite够用了

本来想直接往文本文件append日志,后来发现如果要按端口、按时间查连接记录,还是结构化存储方便。NAS上已经有SQLite,不用额外装数据库,就用SQLite编辑器建了个简单的表:

CREATE TABLE proxy_logs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp TEXT,
    listen_port INTEGER,
    target_port INTEGER,
    client_ip TEXT,
    status TEXT
);

CREATE TABLE port_rules (
    listen_port INTEGER PRIMARY KEY,
    target_port INTEGER,
    enabled INTEGER DEFAULT 1
);

`port_rules`表存端口映射规则,`proxy_logs`记每次连接的时间、来源IP和状态。代码里加几行SQL操作就能把连接事件落库,后面想看哪个端口被访问过多少次,直接查表就行。

HTTP嗅探:想做自动HTTPS,最后放弃了

nasconn+里有个功能我觉得挺酷:检测到HTTP流量后,自动在`原端口+1`上开HTTPS。理论上可以读TCP流量的前几个字节,看是不是`GET / HTTP/1.1`开头,如果是就判定为HTTP。

我试着写了一版,能识别明文请求,但卡在证书这一步:Let's Encrypt的DNS验证要去域名服务商那边改TXT记录,HTTP验证又要保证`.well-known/acme-challenge/`能从外网访问。我这几个容器都是内网自用,没注册域名,搞自签证书浏览器又要手动信任。

折腾半天发现性价比不高,最后这功能直接砍了。

现在的方案就是纯粹的TCP端口转发,不管上层协议。如果真需要HTTPS,我宁愿在应用层自己配证书,或者老老实实上Caddy。

为什么只做了三个端口

最开始我想过做端口扫描:自动检测本机哪些IPv4端口在监听,然后批量给它们开v6代理。但实际跑起来发现两个问题:

  1. **扫描本身有风险**。`netstat`或者`ss`能列出监听端口,但要区分哪些是真正需要外网访问的服务、哪些是数据库或内部通信端口,逻辑会变得很复杂。万一把PostgreSQL的5432也代理出去了,那就是安全事故。
  1. **端口变动不频繁**。我常用的就三个:Jellyfin的8096、qBittorrent的8080、还有一个自建的RSS服务跑在3000。这仨端口半年都不会变,写死在配置里反而最省心。

所以现在的`port_rules`表里就三条记录,代理进程启动时读这张表,只给这三个端口开监听。如果以后要加新服务,手动INSERT一行就行。

怎么让它常驻运行

测试阶段我是直接`python proxy.py`前台跑,关掉SSH连接进程就没了。后来把脚本改成了一个持续监听的服务,用systemd管理(NAS跑的是Debian)。

写了个简单的unit文件:

[Unit]
Description=IPv6 Proxy for Docker Containers
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/scripts/v6proxy.py
Restart=on-failure
RestartSec=10s

[Install]
WantedBy=multi-user.target

`systemctl enable v6proxy`设置开机启动,`systemctl start v6proxy`拉起服务。这样进程挂了会自动重启,日志可以用`journalctl -u v6proxy`查。

如果NAS环境不方便装systemd,也可以把脚本扔到VicroCode的在线工具里做成API端点托管(待核实是否支持长连接socket服务),或者干脆写个cron任务每分钟检查进程是否存活。

目前的使用情况

跑了一个多星期,三个端口的v6访问都正常。从公网用`/ai-market-guide/market/735/

性能上没什么压力,Python的socket转发对我这点流量来说完全够用。内存占用稳定在20MB左右,CPU基本是0%。

如果以后真要处理高并发或者大流量,可能得考虑换Go或者Rust重写,但现在这个规模Python足够了。

没做的事和后续打算

  • **UDP支持**:nasconn+的作者提到UDP还在路上,我这边也没做。主要是我用的服务都是TCP协议,暂时没遇到需求。
  • **SNI嗅探**:如果要做真正的自动HTTPS,应该读TLS握手里的SNI字段,根据域名路由到不同后端。但这需要TLS termination,复杂度一下就上去了。
  • **Web管理界面**:现在改端口映射要手动改SQLite表,有点原始。如果后续端口多了,可以做个简单的HTML表单,用Python的http.server模块提供一个配置页面。

总的来说,这个脚本就是给自己用的小工具,不追求功能完整,只求解决具体问题。如果你也遇到类似场景——容器不支持v6、又不想上重量级反向代理,可以试试这个思路。

代码不复杂,改起来也快。