Feature: Automatically start frp client for subordinate
This commit is contained in:
@@ -10,6 +10,8 @@
|
||||
#include "2015RemoteDlg.h"
|
||||
#include "InputDlg.h"
|
||||
#include "IPHistoryDlg.h"
|
||||
#include "FrpsForSubDlg.h"
|
||||
#include "pwd_gen.h"
|
||||
#include <algorithm>
|
||||
|
||||
// CLicenseDlg 对话框
|
||||
@@ -42,8 +44,14 @@ BEGIN_MESSAGE_MAP(CLicenseDlg, CDialogEx)
|
||||
ON_COMMAND(ID_LICENSE_EDIT_REMARK, &CLicenseDlg::OnLicenseEditRemark)
|
||||
ON_COMMAND(ID_LICENSE_VIEW_IPS, &CLicenseDlg::OnLicenseViewIPs)
|
||||
ON_COMMAND(ID_LICENSE_DELETE, &CLicenseDlg::OnLicenseDelete)
|
||||
ON_COMMAND(ID_LICENSE_AUTO_FRP, &CLicenseDlg::OnLicenseAutoFrp)
|
||||
ON_COMMAND(ID_LICENSE_REVOKE_FRP, &CLicenseDlg::OnLicenseRevokeFrp)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
// 前向声明:实现位于本文件后段(Auto FRP 相关工具)
|
||||
static int ParseRemotePortFromFrpConfig(const std::string& frpConfig);
|
||||
static bool FreeFrpPortAllocation(int port, const std::string& expectedOwner);
|
||||
|
||||
// 获取所有授权信息
|
||||
std::vector<LicenseInfo> GetAllLicenses()
|
||||
{
|
||||
@@ -331,6 +339,15 @@ void CLicenseDlg::OnNMRClickLicenseList(NMHDR* pNMHDR, LRESULT* pResult)
|
||||
const auto& lic = m_Licenses[nIndex];
|
||||
menu.AppendMenuL(MF_STRING, ID_LICENSE_RENEWAL, _T("预设续期(&N)..."));
|
||||
menu.AppendMenuL(MF_STRING, ID_LICENSE_EDIT_REMARK, _T("编辑备注(&E)..."));
|
||||
menu.AppendMenuL(MF_STRING, ID_LICENSE_AUTO_FRP, _T("自动FRP(&F)..."));
|
||||
|
||||
// 仅当该授权已分配 FRPC 端口时,才显示"撤销FRP"
|
||||
{
|
||||
std::string existingFrp = LoadLicenseFrpConfig(lic.SerialNumber);
|
||||
if (ParseRemotePortFromFrpConfig(existingFrp) > 0) {
|
||||
menu.AppendMenuL(MF_STRING, ID_LICENSE_REVOKE_FRP, _T("撤销FRP(&U)"));
|
||||
}
|
||||
}
|
||||
|
||||
// 只有当有 IP 记录时才显示查看 IP 历史选项
|
||||
int ipCount = GetIPCountFromList(lic.IP);
|
||||
@@ -651,6 +668,14 @@ bool DeleteLicense(const std::string& deviceID)
|
||||
return false; // 授权不存在
|
||||
}
|
||||
|
||||
// 若该授权占用了 FRP 端口,先释放 frp_ports.ini 中的占用记录,
|
||||
// 否则端口会一直被这个已不存在的 SN "挂账",导致端口池逐步耗尽。
|
||||
std::string frpConfig = cfg.GetStr(deviceID, "FrpConfig", "");
|
||||
int allocatedPort = ParseRemotePortFromFrpConfig(frpConfig);
|
||||
if (allocatedPort > 0) {
|
||||
FreeFrpPortAllocation(allocatedPort, deviceID);
|
||||
}
|
||||
|
||||
// 删除该 section (通过写入 NULL 删除整个 section)
|
||||
BOOL ret = ::WritePrivateProfileStringA(deviceID.c_str(), NULL, NULL, iniPath.c_str());
|
||||
::WritePrivateProfileStringA(NULL, NULL, NULL, iniPath.c_str()); // 刷新缓存
|
||||
@@ -1010,3 +1035,193 @@ bool FindLicenseByIPAndMachine(const std::string& ip, const std::string& machine
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从 FrpConfig 字符串解析 remotePort
|
||||
// 格式: "serverAddr:serverPort-remotePort-expireDate-authValue"
|
||||
// 注意:serverAddr 可能是含 '-' 的域名(如 my-server.com),必须从右往左定位
|
||||
// 与 CMy2015RemoteDlg::ParseFrpAutoConfig 的拆分策略一致。
|
||||
static int ParseRemotePortFromFrpConfig(const std::string& frpConfig)
|
||||
{
|
||||
if (frpConfig.empty()) return 0;
|
||||
// 倒数第 1 个 '-':分隔 expireDate 与 authValue
|
||||
size_t dashAuth = frpConfig.rfind('-');
|
||||
if (dashAuth == std::string::npos || dashAuth == 0) return 0;
|
||||
std::string s2 = frpConfig.substr(0, dashAuth);
|
||||
// 倒数第 2 个 '-':分隔 remotePort 与 expireDate
|
||||
size_t dashExpire = s2.rfind('-');
|
||||
if (dashExpire == std::string::npos || dashExpire == 0) return 0;
|
||||
std::string s3 = s2.substr(0, dashExpire);
|
||||
// 倒数第 3 个 '-':分隔 serverAddr:serverPort 与 remotePort
|
||||
size_t dashPort = s3.rfind('-');
|
||||
if (dashPort == std::string::npos || dashPort == 0) return 0;
|
||||
return atoi(s3.substr(dashPort + 1).c_str());
|
||||
}
|
||||
|
||||
// 释放 frp_ports.ini 中指定端口的占用记录。
|
||||
// 必须传入 expectedOwner —— 仅当端口当前的归属确实是它时才清除,
|
||||
// 避免在 race 或外部改动后误抹掉别的 SN 的占用记录。
|
||||
static bool FreeFrpPortAllocation(int port, const std::string& expectedOwner)
|
||||
{
|
||||
if (port <= 0 || expectedOwner.empty()) return false;
|
||||
config portsCfg(CFrpsForSubDlg::GetFrpPortsPath());
|
||||
char portStr[16];
|
||||
sprintf_s(portStr, "%d", port);
|
||||
std::string currentOwner = portsCfg.GetStr("ports", portStr, "");
|
||||
if (currentOwner != expectedOwner) {
|
||||
// 已被改写或释放,不动它
|
||||
return false;
|
||||
}
|
||||
portsCfg.SetStr("ports", portStr, ""); // 写入空 owner,FindNextAvailablePort 将其视为可用
|
||||
return true;
|
||||
}
|
||||
|
||||
void CLicenseDlg::OnLicenseAutoFrp()
|
||||
{
|
||||
int nItem = m_ListLicense.GetNextItem(-1, LVNI_SELECTED);
|
||||
if (nItem < 0)
|
||||
return;
|
||||
|
||||
size_t nIndex = (size_t)m_ListLicense.GetItemData(nItem);
|
||||
if (nIndex >= m_Licenses.size())
|
||||
return;
|
||||
|
||||
const auto& lic = m_Licenses[nIndex];
|
||||
|
||||
// 1. 前提条件:FRPS 服务器必须已在「下级 FRP 代理设置」中配置并启用
|
||||
if (!CFrpsForSubDlg::IsFrpsConfigured()) {
|
||||
MessageBoxL("请先在 扩展 → 下级 FRP 代理设置 中启用并配置 FRPS 服务器地址、端口与 Token,然后再使用此功能。",
|
||||
"未配置 FRPS", MB_OK | MB_ICONINFORMATION);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 读取该授权当前的 FRP 配置(若已分配端口)
|
||||
std::string existingFrpConfig = LoadLicenseFrpConfig(lic.SerialNumber);
|
||||
int existingPort = ParseRemotePortFromFrpConfig(existingFrpConfig);
|
||||
|
||||
// 已分配端口时,先询问是否覆盖
|
||||
if (existingPort > 0) {
|
||||
CString msg;
|
||||
msg.FormatL("该授权已分配 FRPC 远程端口 %d,是否覆盖并重新设置?",
|
||||
existingPort);
|
||||
if (MessageBox(msg, _TR("覆盖 FRPC 配置"),
|
||||
MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2) != IDYES) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 决定默认端口:已有端口则沿用,否则查找下一个可用端口
|
||||
int defaultPort = existingPort;
|
||||
if (defaultPort <= 0) {
|
||||
defaultPort = CFrpsForSubDlg::FindNextAvailablePort();
|
||||
if (defaultPort <= 0) {
|
||||
MessageBoxL("FRPS 端口范围已满,无法自动分配,请扩大「下级 FRP 代理设置」中的端口范围。",
|
||||
"端口已满", MB_OK | MB_ICONWARNING);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 弹出输入对话框,允许用户确认或修改端口
|
||||
CInputDialog dlg(this);
|
||||
CString strTitle;
|
||||
strTitle.FormatL("自动 FRP - %s", lic.SerialNumber.c_str());
|
||||
dlg.Init(strTitle, _L("FRPC 远程端口 (1024-65535):"));
|
||||
CString strDefault;
|
||||
strDefault.Format(_T("%d"), defaultPort);
|
||||
dlg.m_str = strDefault;
|
||||
|
||||
if (dlg.DoModal() != IDOK)
|
||||
return;
|
||||
|
||||
int remotePort = _ttoi(dlg.m_str);
|
||||
if (remotePort < 1024 || remotePort > 65535) {
|
||||
MessageBoxL("FRP 远程端口无效(1024-65535)", "提示", MB_OK | MB_ICONWARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. 端口冲突检测:若端口已被其它序列号占用则拒绝
|
||||
std::string portOwner = CFrpsForSubDlg::GetPortOwner(remotePort);
|
||||
if (!portOwner.empty() && portOwner != lic.SerialNumber) {
|
||||
CString msg;
|
||||
msg.FormatL("端口 %d 已被序列号 %s 占用,请选择其它端口。",
|
||||
remotePort, portOwner.c_str());
|
||||
MessageBox(msg, _TR("端口冲突"), MB_OK | MB_ICONWARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. 先生成 FRP 配置串(失败则直接返回,不动 frp_ports.ini)
|
||||
FrpsConfig frpsConfig = CFrpsForSubDlg::GetFrpsConfig();
|
||||
// expireDate 固定 20371231(由 License 自身的过期控制实际有效性)
|
||||
std::string frpConfig = GenerateFrpConfig(
|
||||
frpsConfig.server, frpsConfig.port, remotePort,
|
||||
frpsConfig.token, "20371231", frpsConfig.authMode);
|
||||
|
||||
if (frpConfig.empty()) {
|
||||
MessageBoxL("生成 FRP 配置失败", "错误", MB_OK | MB_ICONERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// 7. 提交端口变更(顺序:新端口 RecordPortAllocation → 释放旧端口 → 写 licenses.ini)。
|
||||
// 顺序保证:任意一步失败都不会出现"旧端口已释放 + 新端口未占用"的真空态。
|
||||
CFrpsForSubDlg::RecordPortAllocation(remotePort, lic.SerialNumber);
|
||||
if (existingPort > 0 && existingPort != remotePort) {
|
||||
FreeFrpPortAllocation(existingPort, lic.SerialNumber); // 仅当旧端口确实归属本 SN 时才释放
|
||||
}
|
||||
{
|
||||
std::string iniPath = GetLicensesPath();
|
||||
config cfg(iniPath);
|
||||
cfg.SetStr(lic.SerialNumber, "FrpConfig", frpConfig);
|
||||
}
|
||||
|
||||
CString msg;
|
||||
msg.FormatL("已为授权 %s 配置 FRPC 远程端口 %d。\n\n下级上线时将自动接收 FRP 配置并启用反向代理。",
|
||||
lic.SerialNumber.c_str(), remotePort);
|
||||
MessageBox(msg, _TR("配置成功"), MB_OK | MB_ICONINFORMATION);
|
||||
}
|
||||
|
||||
void CLicenseDlg::OnLicenseRevokeFrp()
|
||||
{
|
||||
int nItem = m_ListLicense.GetNextItem(-1, LVNI_SELECTED);
|
||||
if (nItem < 0)
|
||||
return;
|
||||
|
||||
size_t nIndex = (size_t)m_ListLicense.GetItemData(nItem);
|
||||
if (nIndex >= m_Licenses.size())
|
||||
return;
|
||||
|
||||
const auto& lic = m_Licenses[nIndex];
|
||||
|
||||
// 读取当前 FRP 配置;若本就没有,菜单理应不显示,这里再防御一次
|
||||
std::string existingFrpConfig = LoadLicenseFrpConfig(lic.SerialNumber);
|
||||
int existingPort = ParseRemotePortFromFrpConfig(existingFrpConfig);
|
||||
if (existingFrpConfig.empty() && existingPort <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 二次确认(撤销后下级下次上线即丢失反向代理通道)
|
||||
CString confirm;
|
||||
confirm.FormatL("确定撤销授权 %s 的 FRP 配置吗?\n\n远程端口 %d 将被释放,下级下次上线后反向代理失效。",
|
||||
lic.SerialNumber.c_str(), existingPort);
|
||||
if (MessageBox(confirm, _TR("撤销 FRP 配置"),
|
||||
MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2) != IDYES) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 先释放 frp_ports.ini 中的端口占用(仅当归属仍为本 SN 时才释放)。
|
||||
// 顺序:先释放端口,再清 FrpConfig —— 即便后一步失败,端口也已可被复用,
|
||||
// 不会留下"FrpConfig 还在但端口已挂账"的悬空状态。
|
||||
if (existingPort > 0) {
|
||||
FreeFrpPortAllocation(existingPort, lic.SerialNumber);
|
||||
}
|
||||
|
||||
// 清除 licenses.ini 中该授权的 FrpConfig 字段
|
||||
{
|
||||
std::string iniPath = GetLicensesPath();
|
||||
config cfg(iniPath);
|
||||
cfg.SetStr(lic.SerialNumber, "FrpConfig", "");
|
||||
}
|
||||
|
||||
CString msg;
|
||||
msg.FormatL("已撤销授权 %s 的 FRP 配置,远程端口 %d 已释放。",
|
||||
lic.SerialNumber.c_str(), existingPort);
|
||||
MessageBox(msg, _TR("撤销成功"), MB_OK | MB_ICONINFORMATION);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user