如何在 NAT1 下实现无感 PLEX 外网访问

2024 年 10 月 28 日 星期一(已编辑)
/ , ,
54
这篇文章上次修改于 2025 年 3 月 26 日 星期三,可能部分内容已经不适用,如有疑问可询问作者。

如何在 NAT1 下实现无感 PLEX 外网访问

效果

能在没有公网,仅拥有 NAT1 的情况下,做到无感 PLEX 外网访问

前提

  • 确保路由器的 NAT 类型为 NAT1,并且要求 TCP 的 NAT 类型也为 NAT1
  • 主路由场景下基本没什么大问题,如果在旁路由场景下可能需要为旁路由开启 DMZ,此外,OpenClash等科学插件也有影响
  • 安装 Lucky,这里不多赘述,自行解决

相关链接

配置

脚本配置

  • Openwrt 为例,首先确保安装Python,随后安装下列库
pip3 install plexapi
  • 根据其中需要修改的四行代码,修改为自己的实际参数,随后复制并建立一个.py文件,例如 modify_port.py,放置于任意位置,例如/root路径下

这是需要修改的内容

# Plex 账号信息
PLEX_USERNAME = "example@qq.com"
PLEX_PASSWORD = "password"

PLEX_SERVER_BASEURL = "http://192.168.3.6:32400"  # 替换为你的 Plex 服务器 URL

TOKEN_FILE = "/root/plex_token.json"  # 保存 PLEX Token 的路径

这是脚本代码

import os
import sys
import json
import requests
from plexapi.server import PlexServer

# Plex 账号信息
PLEX_USERNAME = "example@qq.com"
PLEX_PASSWORD = "password"

# Plex API URL
LOGIN_URL = "https://plex.tv/users/sign_in.json"
PLEX_SERVER_BASEURL = "http://192.168.3.6:32400"  # 替换为你的 Plex 服务器 URL

# 登录请求的头信息
HEADERS = {
    "Content-Type": "application/json",
    "Accept": "application/json",
    "X-Plex-Client-Identifier": "MyPlexApp"
}

# 获取新令牌的登录请求数据
data = {
    "user": {
        "login": PLEX_USERNAME,
        "password": PLEX_PASSWORD
    }
}

def get_plex_token():
    """
    通过向 Plex API 发送登录请求获取 Plex 认证令牌。
    """
    try:
        response = requests.post(LOGIN_URL, headers=HEADERS, data=json.dumps(data))
        if response.status_code == 201:  # 状态码 201 表示登录成功
            return response.json()['user']['authentication_token']
        else:
            print(f"获取令牌失败。状态码: {response.status_code}")
            return None
    except Exception as e:
        print(f"认证过程中发生错误: {str(e)}")
        return None

def read_token_from_file(token_file):
    """
    从指定的 JSON 文件中读取 Plex 令牌。
    """
    if os.path.exists(token_file):
        try:
            with open(token_file, 'r') as file:
                data = json.load(file)
                return data.get('token')
        except Exception as e:
            print(f"读取令牌文件时发生错误: {str(e)}")
            return None
    else:
        return None

def save_token_to_file(token_file, token):
    """
    将 Plex 令牌保存到指定的 JSON 文件中。
    """
    try:
        with open(token_file, 'w') as file:
            json.dump({"token": token}, file)
    except Exception as e:
        print(f"保存令牌到文件时发生错误: {str(e)}")

def check_token_validity(baseurl, token):
    """
    通过向服务器发送验证请求来检查 Plex 令牌的有效性。
    """
    try:
        check_url = f"{baseurl}/library/sections?X-Plex-Token={token}"
        response = requests.get(check_url)
        return response.status_code == 200
    except Exception as e:
        print(f"检查令牌有效性时发生错误: {str(e)}")
        return False

def modify_port(plex, new_port):
    """
    修改 Plex 服务器设置中的手动端口映射。
    """
    manual_port_setting = plex.settings.get('manualPortMappingPort')
  
    if manual_port_setting:
        try:
            manual_port_setting.set(new_port)
            plex.settings.save()
            print(f"手动端口映射已成功更改为: {new_port}")
        except Exception as e:
            print(f"修改端口或保存设置时发生错误: {str(e)}")
    else:
        print("无法找到手动端口映射设置。")

if __name__ == "__main__":
    # 第一步:解析命令行参数,获取新的端口号
    if len(sys.argv) < 2:
        print("用法: python3 modify.py <new_port>")
        sys.exit(1)

    try:
        NEW_PORT = int(sys.argv[1])  # 将提供的端口号转换为整数
    except ValueError:
        print("无效的端口号。请提供有效的整数。")
        sys.exit(1)

    # 第二步:定义令牌存储文件
    TOKEN_FILE = "/root/plex_token.json"  # Token路径

    # 第三步:从文件中读取令牌或生成新令牌
    PLEX_TOKEN = read_token_from_file(TOKEN_FILE)

    # 第四步:如果文件中没有令牌,或者令牌无效,则生成新令牌
    if PLEX_TOKEN is None or not check_token_validity(PLEX_SERVER_BASEURL, PLEX_TOKEN):
        PLEX_TOKEN = get_plex_token()
        if PLEX_TOKEN:
            save_token_to_file(TOKEN_FILE, PLEX_TOKEN)
        else:
            print("获取有效令牌失败。")
            sys.exit(1)

    # 第五步:使用 plexapi 连接到 Plex 服务器并修改手动端口
    try:
        plex = PlexServer(PLEX_SERVER_BASEURL, PLEX_TOKEN)
        print("成功连接到 Plex 服务器!")
  
        # 修改手动端口映射(使用传入的端口号)
        modify_port(plex, NEW_PORT)
    except Exception as e:
        print(f"连接 Plex 服务器或修改设置时发生错误: {str(e)}")

Lucky 配置

LUCKY

LUCKY

创建一个 TCP 穿透规则,这里我指定了一个空闲端口为 13333,并且不使用 LUCKY 的内置端口转发。开启自定义脚本触发,代码如下:

python3 /root/modify_port.py ${port}

if [ $? -ne 0 ]; then
    echo "Python script execution failed with exit code $?."
fi

注意此处代码的 /root/modify_port.py 就是刚刚的python执行脚本的实际路径。

随后确认即可,规则创建关闭后稍时即可打洞成功。

端口转发

由于我们刚刚指定了端口,并且没有使用Lucky内置的端口转发,因此我们需要用防火墙配置手动端口转发

端口转发

端口转发

如果你是主路由,源区域应该是防火墙的“WAN”区域,我是旁路由,所以源区域为LAN

外部端口就是刚刚打洞绑定的端口,例如我刚刚在 LUCKY 配置了13333端口

目前区域即可 LAN,内部 IP 地址即为 PLEX 所在主机 IP,内部端口通常为 PLEX Server 默认端口32400

常见问题

  • 在配置前可单独调试脚本,确保脚本正常运作,脚本调试命令为:
python3 modify_port.py <port>
  • 旁路由的可能额外操作 LUCKY 位于旁路由,并且使用了 Openclash,即使配置了旁路由为 DMZ,但是 TCP 流量经过了 Openclash,因此导致了 TCP 的 NAT 类型不为 NAT1。

解决方案有下列几种:

  1. 通过 Openclash 的仅允许常用端口流量功能,可以让打洞的流量不经过 Openclash,即不包含3478端口流量
  2. 通过 Openclash 的黑白名单功能中的绕过核心的来源端口功能

    旁路由

    旁路由

设置几个打洞时绑定的预设端口,这些端口流量就不会经过 Openclash 了,但配置中也指出了,Fake ip模式下只能过滤纯 IP 类型请求。

其它请自行发挥,包括但不限于使用Openclash的开发者选项,或者修改防火墙来实现

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...