web: reset links to the default mode, and keep CN agents dial-only by default [CI]
POST /api/admin/links/reset clears every manual link setting and turns automatic linking on. In the default rules a listening node in CN ranks below a listening node elsewhere, so the CN side dials; the links are pushed again when a node's country is first resolved or changes.
This commit is contained in:
1 parent
35207a70ea
commit
7fe9775f97
8 files changed
+222
-15
No files matched your search
@@ -80,6 +80,8 @@ public sealed partial class AgentService
|
||||
_gateway = gateway;
|
||||
_planner = planner;
|
||||
_settings = settings;
|
||||
// 线路的默认方向看国家(国内的节点默认只拨出):国家查到或变了就重新下发
|
||||
_nodes.OnCountryChanged = () => AfterAgentSetChangedAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
public async ValueTask<IReadOnlyList<AgentView>> ListAsync(CancellationToken ct)
|
||||
|
||||
@@ -4,11 +4,13 @@ namespace MoShiro.Backend.Application;
|
||||
public sealed class LinkSnapshot
|
||||
{
|
||||
private readonly Dictionary<(string Dialer, string Listener), bool> _overrides;
|
||||
private readonly HashSet<string> _domestic;
|
||||
|
||||
public LinkSnapshot(bool auto, Dictionary<(string Dialer, string Listener), bool> overrides)
|
||||
public LinkSnapshot(bool auto, Dictionary<(string Dialer, string Listener), bool> overrides, HashSet<string>? domestic = null)
|
||||
{
|
||||
Auto = auto;
|
||||
_overrides = overrides;
|
||||
_domestic = domestic ?? [];
|
||||
}
|
||||
|
||||
/// <summary>自动连接:开着时,每一对节点自动连成一条线路;关着时,只有手动开启的才连。</summary>
|
||||
@@ -21,11 +23,25 @@ public sealed class LinkSnapshot
|
||||
public bool? PairOverride(string dialerId, string listenerId) => OverrideOf(dialerId, listenerId) ?? OverrideOf(listenerId, dialerId);
|
||||
|
||||
/// <summary>
|
||||
/// 自动连接时这一对由谁拨:只有一方监听,就由不监听的一方拨;两方都监听,由 id 较小的一方拨。
|
||||
/// 一对节点只有一条线路、一个连接、一份 RTT;上下行都在这条连接上测,不再各拨一次。
|
||||
/// 谁更适合当被连接的一方(越大越适合):不监听的是 0;监听的是 2,但国内(国家 CN)的监听节点是 1——国内节点默认只拨出,
|
||||
/// 除非对端没有更好的选择。手动指定的方向不受这个影响。
|
||||
/// </summary>
|
||||
public static bool IsDefaultDialer(AgentRecord dialer, AgentRecord listener) =>
|
||||
listener.Listens && (!dialer.Listens || string.CompareOrdinal(dialer.Id.Value, listener.Id.Value) < 0);
|
||||
private int ListenerRank(AgentRecord node) => !node.Listens ? 0 : _domestic.Contains(node.Id.Value) ? 1 : 2;
|
||||
|
||||
/// <summary>
|
||||
/// 自动连接时这一对由谁拨:更适合当被连接方的一方被拨;一样适合时 id 较小的一方拨。
|
||||
/// 所以不监听的拨监听的,国内的拨国外的,其余按 id。一对节点只有一条线路、一个连接、一份 RTT;上下行都在这条连接上测。
|
||||
/// </summary>
|
||||
public bool IsDefaultDialer(AgentRecord dialer, AgentRecord listener)
|
||||
{
|
||||
if (!listener.Listens)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int rank = ListenerRank(listener) - ListenerRank(dialer);
|
||||
return rank > 0 || (rank == 0 && string.CompareOrdinal(dialer.Id.Value, listener.Id.Value) < 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 这一对最终连不连、是不是由这个方向拨:手动设置优先(<c>true</c> 的方向就是拨号方,两个方向都设了 <c>true</c> 时按默认方向),
|
||||
@@ -60,7 +76,7 @@ public sealed class LinkSnapshot
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 哪些(拨号方,监听方)要连成线路。由面板决定、拨号方主动拨出:每一对节点只有一条线路,自动连接开着时都连(谁拨见 <see cref="LinkSnapshot.IsDefaultDialer"/>),
|
||||
/// 哪些(拨号方,监听方)要连成线路。由面板决定、拨号方主动拨出:每一对节点只有一条线路,自动连接开着时都连(谁拨见 <see cref="LinkSnapshot.IsDefaultDialer"/>,国内节点默认只拨出),
|
||||
/// 关着时只连手动选中的;每一对还可以手动强制开或关,并且用 <c>true</c> 选定由哪一方拨。手动设置存在设置表里(<c>link.<拨号方>.<监听方></c>,值 1 或 0,空表示没设置),一对节点最多保留一条。
|
||||
/// 判断结果分别用在拨号方的拨号目标、监听方允许的拨号方(<c>peers</c>)和线路目录里;改动之后要调用 <see cref="AgentService.AfterAgentSetChangedAsync"/> 重新下发。
|
||||
/// </summary>
|
||||
@@ -69,10 +85,12 @@ public sealed class LinkService
|
||||
private const string Prefix = "link.";
|
||||
|
||||
private readonly ISettingsStore _settings;
|
||||
private readonly NodeInfoService _nodes;
|
||||
|
||||
public LinkService(ISettingsStore settings)
|
||||
public LinkService(ISettingsStore settings, NodeInfoService nodes)
|
||||
{
|
||||
_settings = settings;
|
||||
_nodes = nodes;
|
||||
}
|
||||
|
||||
public async ValueTask<LinkSnapshot> SnapshotAsync(CancellationToken ct)
|
||||
@@ -88,7 +106,8 @@ public sealed class LinkService
|
||||
}
|
||||
}
|
||||
|
||||
return new LinkSnapshot(all.GetValueOrDefault(SettingsService.AutoLink) != "false", overrides);
|
||||
HashSet<string> domestic = [.. (await _nodes.ListAsync(ct)).Where(n => n.Country == "CN").Select(n => n.NodeId)];
|
||||
return new LinkSnapshot(all.GetValueOrDefault(SettingsService.AutoLink) != "false", overrides, domestic);
|
||||
}
|
||||
|
||||
/// <summary>手动设置一对线路:<c>true</c> 强制连并由 <paramref name="dialerId"/> 拨、<c>false</c> 强制不连、<c>null</c> 取消手动设置。反方向的旧设置同时清掉。</summary>
|
||||
@@ -98,6 +117,17 @@ public sealed class LinkService
|
||||
await _settings.SetAsync($"{Prefix}{dialerId}.{listenerId}", enabled is null ? "" : enabled.Value ? "1" : "0", ct);
|
||||
}
|
||||
|
||||
/// <summary>恢复默认连接模式:清掉所有手动设置并打开自动连接,一对节点谁拨谁完全按默认规则。</summary>
|
||||
public async ValueTask ResetAsync(CancellationToken ct)
|
||||
{
|
||||
foreach (string key in (await _settings.GetAllAsync(ct)).Where(kv => kv.Key.StartsWith(Prefix, StringComparison.Ordinal) && kv.Value.Length > 0).Select(kv => kv.Key))
|
||||
{
|
||||
await _settings.SetAsync(key, "", ct);
|
||||
}
|
||||
|
||||
await _settings.SetAsync(SettingsService.AutoLink, "true", ct);
|
||||
}
|
||||
|
||||
/// <summary>节点被删除:清掉所有涉及它的手动设置。</summary>
|
||||
public async ValueTask ForgetNodeAsync(string nodeId, CancellationToken ct)
|
||||
{
|
||||
|
||||
@@ -29,6 +29,9 @@ public sealed class NodeInfoService
|
||||
private readonly ConcurrentDictionary<string, long> _lastTry = new();
|
||||
private Task? _load;
|
||||
|
||||
/// <summary>某个节点的国家变了(第一次查到或查到了不同的结果)时调用:线路的默认方向取决于国家,需要重新下发。</summary>
|
||||
public Func<ValueTask>? OnCountryChanged { get; set; }
|
||||
|
||||
public NodeInfoService(INodeMetaStore store, IGeoLookup geo, TimeProvider time)
|
||||
{
|
||||
_store = store;
|
||||
@@ -148,15 +151,22 @@ public sealed class NodeInfoService
|
||||
}
|
||||
|
||||
string country = geo.Country.ToUpperInvariant();
|
||||
bool countryChanged = !string.Equals(current.Info.Country, country, StringComparison.Ordinal);
|
||||
if (current.Info.IspManual)
|
||||
{
|
||||
// 运营商是手动指定的:只更新国家,不覆盖运营商
|
||||
await SaveAsync(current with { Info = current.Info with { Country = country }, GeoHost = host, GeoAtMs = now, UpdatedAtMs = now }, ct);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
(string key, string name) = IspClassifier.Classify(geo.Asn, geo.Organization);
|
||||
await SaveAsync(current with { Info = current.Info with { Country = country, IspKey = key, IspName = Clean(name, 40) }, GeoHost = host, GeoAtMs = now, UpdatedAtMs = now }, ct);
|
||||
}
|
||||
|
||||
(string key, string name) = IspClassifier.Classify(geo.Asn, geo.Organization);
|
||||
await SaveAsync(current with { Info = current.Info with { Country = country, IspKey = key, IspName = Clean(name, 40) }, GeoHost = host, GeoAtMs = now, UpdatedAtMs = now }, ct);
|
||||
if (countryChanged && OnCountryChanged is { } notify)
|
||||
{
|
||||
await notify();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user