## 效果
能在没有公网,仅拥有 NAT1 的情况下,做到无感 PLEX 外网访问
## 前提
* 确保路由器的 NAT 类型为 NAT1,并且要求 TCP 的 NAT 类型也为 NAT1
* 主路由场景下基本没什么大问题,如果在旁路由场景下可能需要为旁路由开启 DMZ ,此外,Openclash 等科学插件也有影响
* 安装 Lucky ,这里不多赘述,自行解决
### 相关链接
[Lucky 项目地址]( https://github.com/gdy666/lucky)
[Python-plexapi]( https://python-plexapi.readthedocs.io/en/latest/introduction.html)
## 配置
**脚本配置**
* 以 **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 的路径
```
**这是脚本代码**
```python
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 配置**
![CleanShot 2024-10-28 at 15.23.49@2x.png]( https://s2.loli.net/2024/10/28/rh7jfM2tWVHp8PN.png)
创建一个 TCP 穿透规则,这里我指定了一个空闲端口为 13333 ,并且不使用 LUCKY 的内置端口转发。开启自定义脚本触发,代码如下:
```bash
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 内置的端口转发,因此我们需要用防火墙配置手动端口转发
![CleanShot 2024-10-28 at 15.35.16@2x.png]( https://s2.loli.net/2024/10/28/K4RWq2GXJVhP1eC.png)
如果你是主路由,源区域应该是防火墙的“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 的黑白名单功能中的绕过核心的来源端口功能
![CleanShot 2024-10-28 at 15.49.18@2x.png]( https://s2.loli.net/2024/10/28/ZIX5gd6QGwCz7NT.png)
设置几个打洞时绑定的预设端口,这些端口流量就不会经过 Openclash 了,但配置中也指出了,Fake ip 模式下只能过滤纯 IP 类型请求。
其它请自行发挥,包括但不限于使用 Openclash 的开发者选项,或者修改防火墙来实现 |
|