Skip to content

Commit 685d65d

Browse files
committed
Improved handling for pool connection endpoint rotation
1 parent 32bd98f commit 685d65d

2 files changed

Lines changed: 142 additions & 82 deletions

File tree

GostGen/source/GostConfig.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ internal record SelectorConfig
402402
public int? MaxFails { get; set; }
403403

404404
[YamlMember(Alias = "failTimeout")]
405-
public TimeSpan? FailTimeout { get; set; }
405+
public string? FailTimeout { get; set; }
406406
}
407407

408408
internal enum SelectorStrategy

GostGen/source/Program.cs

Lines changed: 141 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ public class Program
3636
private const int ProxyPortCitiesEnd = 3000;
3737
private const int ProxiesServersPerCity = 9;
3838
private const int ProxyPortsPerCity = ProxiesServersPerCity + 1;
39+
private const int PoolSelectorMaxFails = 1;
40+
private const string PoolSelectorFailTimeout = "10s";
41+
private const SelectorStrategy PoolSelectorStrategy = SelectorStrategy.round;
3942

4043
private static readonly Regex AddressProtRegex = new(@":(?<port>\d+)$", RegexOptions.Compiled);
4144

@@ -349,117 +352,140 @@ private static async Task<bool> UpdateGostServersAsync(GostConfig gostConfig)
349352
{
350353
var servers = city.Where(s => !string.IsNullOrWhiteSpace(s.SocksName)).OrderBy(c => c.Hostname).ToArray();
351354
if (!servers.Any()) continue;
352-
var servicePoolName = $"service-{city.First().CountryCode}-{city.First().CityCode}-pool".ToLower();
353-
var chainPoolName = $"chain-{city.First().CountryCode}-{city.First().CityCode}-pool".ToLower();
354-
var chainPoolHopName = $"hop-{chainPoolName}";
355-
var chainPoolHopNodeName = $"node-{chainPoolHopName}" + "-{0}";
356-
var poolPort = FindNextFreeCityPortAreaStart(gostConfig, servicePoolName);
357-
if (poolPort < 1) continue;
358-
cfgChanged |= CreateOrUpdateProxy(gostConfig, servers, servicePoolName, chainPoolName, chainPoolHopName, chainPoolHopNodeName, poolPort);
359-
360-
var serverCnt = Math.Min(servers.Length, ProxiesServersPerCity);
361-
for (var i = 1; i <= serverCnt; i++)
362-
{
363-
var server = servers[i - 1];
364-
var serviceCityName = $"service-{server.CountryCode}-{server.CityCode}-{i}".ToLower();
365-
var chainCityName = $"chain-{server.CountryCode}-{server.CityCode}-{i}".ToLower();
366-
var chainCityHopName = $"hop-{chainCityName}";
367-
var chainCityHopNodeName = $"node-{chainCityHopName}";
368-
var port = poolPort + i;
369-
cfgChanged |= CreateOrUpdateProxy(gostConfig, [server], serviceCityName, chainCityName, chainCityHopName, chainCityHopNodeName, port);
370-
}
355+
cfgChanged |= CreateOrUpdateProxy(gostConfig, city.First().CountryCode!, city.First().CityCode!, servers);
371356
}
372357
}
373358

374359
return cfgChanged;
375360
}
376361

377-
private static int FindNextFreeCityPortAreaStart(GostConfig gostConfig, string servicePoolName)
378-
{
379-
gostConfig.Services ??= [];
380-
381-
// Reuse existing poot port
382-
var service = gostConfig.Services.FirstOrDefault(s => string.Equals(s.Name, servicePoolName, StringComparison.OrdinalIgnoreCase));
383-
if (AddressProtRegex.IsMatch(service?.Addr ?? string.Empty))
384-
return int.Parse(AddressProtRegex.Match(service!.Addr!).Groups["port"].Value);
385-
386-
// ToDo: More efficient way to find free port area that also supports free areas between used ports (e.g. 2000-2009 used, 2010-2019 free, 2020-2029 used -> assign next pool to 2010-2019)
387-
388-
// Find next free port area
389-
var usedPorts = gostConfig.Services.Where(s => AddressProtRegex.IsMatch(s.Addr ?? string.Empty))
390-
.Select(s => int.Parse(AddressProtRegex.Match(s.Addr!).Groups["port"].Value))
391-
.Where(p => p >= ProxyPortCitiesStart).ToArray();
392-
if (!usedPorts.Any()) return ProxyPortCitiesStart;
393-
var lastPort = usedPorts.Max();
394-
if (lastPort + 1 % ProxyPortsPerCity != 0) lastPort = (lastPort / ProxyPortsPerCity + 1) * ProxyPortsPerCity; // Round up to next multiple of ProxyPortsPerCity (10)
395-
lastPort = Math.Max(lastPort, ProxyPortCitiesStart); // Limit to start of city proxy port area
396-
if (lastPort + ProxyPortsPerCity < ProxyPortCitiesEnd) // Limit to end of city proxy port area
397-
return lastPort;
398-
399-
Log.Error($"Unable to find free port area for city pool `{servicePoolName}` since last used port `{lastPort}` is to close to end of city proxy port area ({ProxyPortCitiesStart}-{ProxyPortCitiesEnd})");
400-
return -1;
401-
}
402-
403-
private static bool CreateOrUpdateProxy(GostConfig gostConfig, IEnumerable<MullvadRelay> servers, string servicePoolName, string chainPoolName, string chainHopName, string chainHopNodeName, int port)
362+
private static bool CreateOrUpdateProxy(GostConfig gostConfig, string countryCode, string cityCode, MullvadRelay[] servers)
404363
{
405364
var cfgChanged = false;
365+
var servicePoolName = $"service-{countryCode}-{cityCode}-pool".ToLower();
366+
var chainPoolName = $"chain-{countryCode}-{cityCode}-pool".ToLower();
367+
var chainPoolHopName = $"hop-{chainPoolName}".ToLower();
368+
var poolPort = FindNextFreeCityPortAreaStart(gostConfig, servicePoolName);
369+
if (poolPort < 1) return false;
370+
var poolServiceAddress = $":{poolPort}";
371+
406372
gostConfig.Services ??= [];
407-
var service = gostConfig.Services.FirstOrDefault(s => string.Equals(s.Name, servicePoolName, StringComparison.OrdinalIgnoreCase));
408-
if (service == null)
373+
var poolService = gostConfig.Services.FirstOrDefault(s => string.Equals(s.Name, servicePoolName, StringComparison.OrdinalIgnoreCase));
374+
if (poolService == null)
409375
{
410-
Log.Debug($"Adding proxy pool `{servicePoolName}`");
411-
service = new() { Name = servicePoolName };
412-
gostConfig.Services.Add(service);
376+
Log.Debug($"Adding proxy pool service `{servicePoolName}`");
377+
poolService = new() { Name = servicePoolName };
378+
gostConfig.Services.Add(poolService);
413379
cfgChanged = true;
414380
}
415381

416-
gostConfig.Chains ??= [];
417-
var chain = gostConfig.Chains.FirstOrDefault(c => string.Equals(c.Name, chainPoolName, StringComparison.OrdinalIgnoreCase));
418-
if (chain == null)
382+
if (poolService.Addr != poolServiceAddress ||
383+
!string.Equals(poolService.Interface, InputInterfaceName) ||
384+
!string.Equals(poolService.Listener?.Type, NetworkProtocol) ||
385+
!string.Equals(poolService.Handler?.Type, SocksType) ||
386+
!string.Equals(poolService.Handler?.Auther, AutherMullvadGroup) ||
387+
!string.Equals(poolService.Handler?.Chain, chainPoolName))
419388
{
420-
Log.Debug($"Adding proxy pool `{chainPoolName}`");
421-
chain = new() { Name = chainPoolName };
422-
gostConfig.Chains.Add(chain);
389+
Log.Debug($"Updating proxy pool `{servicePoolName}`");
390+
poolService.Addr = poolServiceAddress;
391+
poolService.Interface = InputInterfaceName;
392+
poolService.Listener = new() { Type = NetworkProtocol };
393+
poolService.Handler = new() { Type = SocksType, Auther = AutherMullvadGroup, Chain = chainPoolName };
423394
cfgChanged = true;
424395
}
425396

426-
chain.Hops ??= [];
427-
var hop = chain.Hops.FirstOrDefault(h => string.Equals(h.Name, chainHopName, StringComparison.OrdinalIgnoreCase));
428-
if (hop == null)
397+
gostConfig.Chains ??= [];
398+
var poolChain = gostConfig.Chains.FirstOrDefault(c => string.Equals(c.Name, chainPoolName, StringComparison.OrdinalIgnoreCase));
399+
if (poolChain == null)
429400
{
430-
Log.Debug($"Adding proxy hop `{chainHopName}`");
431-
hop = new() { Name = chainHopName };
432-
chain.Hops.Add(hop);
401+
Log.Debug($"Adding proxy pool chain `{chainPoolName}`");
402+
poolChain = new() { Name = chainPoolName };
403+
gostConfig.Chains.Add(poolChain);
433404
cfgChanged = true;
434405
}
435406

436-
var portMatch = AddressProtRegex.Match(service.Addr ?? string.Empty);
437-
if (!portMatch.Success || int.Parse(portMatch.Groups["port"].Value) < 1 ||
438-
!string.Equals(service.Interface, InputInterfaceName) ||
439-
!string.Equals(service.Listener?.Type, NetworkProtocol) ||
440-
!string.Equals(service.Handler?.Type, SocksType) ||
441-
!string.Equals(service.Handler?.Auther, AutherMullvadGroup) ||
442-
!string.Equals(service.Handler?.Chain, chainPoolName))
407+
poolChain.Hops ??= [];
408+
var poolHop = poolChain.Hops.FirstOrDefault(h => string.Equals(h.Name, chainPoolHopName, StringComparison.OrdinalIgnoreCase));
409+
if (poolHop == null)
443410
{
444-
Log.Debug($"Updating proxy pool `{servicePoolName}`");
445-
service.Addr = $":{port}";
446-
service.Interface = InputInterfaceName;
447-
service.Listener = new() { Type = NetworkProtocol };
448-
service.Handler = new() { Type = SocksType, Auther = AutherMullvadGroup, Chain = chainPoolName };
411+
Log.Debug($"Adding pool hop `{chainPoolHopName}`");
412+
poolHop = new() { Name = chainPoolHopName };
413+
poolChain.Hops.Add(poolHop);
449414
cfgChanged = true;
450415
}
451416

452-
hop.Nodes ??= [];
417+
poolHop.Nodes ??= [];
418+
poolHop.Selector ??= new();
419+
if (poolHop.Selector.Strategy != PoolSelectorStrategy ||
420+
poolHop.Selector.MaxFails != PoolSelectorMaxFails ||
421+
poolHop.Selector.FailTimeout != PoolSelectorFailTimeout)
422+
{
423+
Log.Debug($"Updating pool selector of `{chainPoolHopName}`");
424+
poolHop.Selector.Strategy = PoolSelectorStrategy;
425+
poolHop.Selector.MaxFails = PoolSelectorMaxFails;
426+
poolHop.Selector.FailTimeout = PoolSelectorFailTimeout;
427+
cfgChanged = true;
428+
}
429+
453430
var serverCnt = Math.Min(servers.Count(), ProxiesServersPerCity);
454431
for (var i = 1; i <= serverCnt; i++)
455432
{
433+
var serviceCityName = $"service-{countryCode}-{cityCode}-{i}".ToLower();
434+
var chainCityName = $"chain-{countryCode}-{cityCode}-{i}".ToLower();
435+
var chainCityHopName = $"hop-{chainCityName}";
436+
var chainCityHopNodeName = $"node-{chainCityHopName}";
437+
var cityAddress = $":{poolPort + i}";
438+
439+
var cityService = gostConfig.Services.FirstOrDefault(s => string.Equals(s.Name, serviceCityName, StringComparison.OrdinalIgnoreCase));
440+
if (cityService == null)
441+
{
442+
Log.Debug($"Adding proxy city `{serviceCityName}`");
443+
cityService = new() { Name = serviceCityName };
444+
gostConfig.Services.Add(cityService);
445+
cfgChanged = true;
446+
}
447+
448+
if (cityService.Addr != cityAddress ||
449+
!string.Equals(cityService.Interface, InputInterfaceName) ||
450+
!string.Equals(cityService.Listener?.Type, NetworkProtocol) ||
451+
!string.Equals(cityService.Handler?.Type, SocksType) ||
452+
!string.Equals(cityService.Handler?.Auther, AutherMullvadGroup) ||
453+
!string.Equals(cityService.Handler?.Chain, chainCityName))
454+
{
455+
Log.Debug($"Updating proxy city `{servicePoolName}`");
456+
cityService.Addr = cityAddress;
457+
cityService.Interface = InputInterfaceName;
458+
cityService.Listener = new() { Type = NetworkProtocol };
459+
cityService.Handler = new() { Type = SocksType, Auther = AutherMullvadGroup, Chain = chainCityName };
460+
cfgChanged = true;
461+
}
462+
463+
var chain = gostConfig.Chains.FirstOrDefault(c => string.Equals(c.Name, chainCityName, StringComparison.OrdinalIgnoreCase));
464+
if (chain == null)
465+
{
466+
Log.Debug($"Adding proxy chain `{chainCityName}`");
467+
chain = new() { Name = chainCityName };
468+
gostConfig.Chains.Add(chain);
469+
cfgChanged = true;
470+
}
471+
472+
chain.Hops ??= [];
473+
var hop = chain.Hops.FirstOrDefault(h => string.Equals(h.Name, chainCityHopName, StringComparison.OrdinalIgnoreCase));
474+
if (hop == null)
475+
{
476+
Log.Debug($"Adding city hop `{chainCityHopName}`");
477+
hop = new() { Name = chainCityHopName };
478+
chain.Hops.Add(hop);
479+
cfgChanged = true;
480+
}
481+
482+
hop.Nodes ??= [];
456483
var server = servers.ElementAt(i - 1);
457-
var nodeName = string.Format(chainHopNodeName, i);
458-
var node = hop.Nodes.FirstOrDefault(n => string.Equals(n.Name, nodeName, StringComparison.OrdinalIgnoreCase));
484+
var node = hop.Nodes.FirstOrDefault(n => string.Equals(n.Name, chainCityHopNodeName, StringComparison.OrdinalIgnoreCase));
459485
if (node == null)
460486
{
461-
Log.Debug($"Adding proxy node `{nodeName}`");
462-
node = new() { Name = nodeName };
487+
Log.Debug($"Adding city node `{chainCityHopNodeName}`");
488+
node = new() { Name = chainCityHopNodeName };
463489
hop.Nodes.Add(node);
464490
cfgChanged = true;
465491
}
@@ -470,15 +496,49 @@ private static bool CreateOrUpdateProxy(GostConfig gostConfig, IEnumerable<Mullv
470496
!string.Equals(node.Connector?.Type, SocksType) ||
471497
!string.Equals(node.Dialer?.Type, NetworkProtocol))
472498
{
473-
Log.Debug($"Updating proxy node `{nodeName}`");
499+
Log.Debug($"Updating city node `{chainCityHopNodeName}`");
474500
node.Bypass = BypassMullvadGroup;
475501
node.Addr = svrAddress;
476502
node.Connector = new() { Type = SocksType };
477503
node.Dialer = new() { Type = NetworkProtocol };
478504
cfgChanged = true;
479505
}
506+
507+
var poolNode = node with { Name = $"{chainCityHopNodeName}-pool" };
508+
if (poolHop.Nodes.ElementAtOrDefault(i - 1) != poolNode)
509+
{
510+
Log.Debug($"Updating pool node `{poolNode.Name}`");
511+
poolHop.Nodes.Insert(i - 1, poolNode);
512+
cfgChanged = true;
513+
}
480514
}
481515

482516
return cfgChanged;
483517
}
518+
519+
private static int FindNextFreeCityPortAreaStart(GostConfig gostConfig, string servicePoolName)
520+
{
521+
gostConfig.Services ??= [];
522+
523+
// Reuse existing poot port
524+
var service = gostConfig.Services.FirstOrDefault(s => string.Equals(s.Name, servicePoolName, StringComparison.OrdinalIgnoreCase));
525+
if (AddressProtRegex.IsMatch(service?.Addr ?? string.Empty))
526+
return int.Parse(AddressProtRegex.Match(service!.Addr!).Groups["port"].Value);
527+
528+
// ToDo: More efficient way to find free port area that also supports free areas between used ports (e.g. 2000-2009 used, 2010-2019 free, 2020-2029 used -> assign next pool to 2010-2019)
529+
530+
// Find next free port area
531+
var usedPorts = gostConfig.Services.Where(s => AddressProtRegex.IsMatch(s.Addr ?? string.Empty))
532+
.Select(s => int.Parse(AddressProtRegex.Match(s.Addr!).Groups["port"].Value))
533+
.Where(p => p >= ProxyPortCitiesStart).ToArray();
534+
if (!usedPorts.Any()) return ProxyPortCitiesStart;
535+
var lastPort = usedPorts.Max();
536+
if (lastPort + 1 % ProxyPortsPerCity != 0) lastPort = (lastPort / ProxyPortsPerCity + 1) * ProxyPortsPerCity; // Round up to next multiple of ProxyPortsPerCity (10)
537+
lastPort = Math.Max(lastPort, ProxyPortCitiesStart); // Limit to start of city proxy port area
538+
if (lastPort + ProxyPortsPerCity < ProxyPortCitiesEnd) // Limit to end of city proxy port area
539+
return lastPort;
540+
541+
Log.Error($"Unable to find free port area for city pool `{servicePoolName}` since last used port `{lastPort}` is to close to end of city proxy port area ({ProxyPortCitiesStart}-{ProxyPortCitiesEnd})");
542+
return -1;
543+
}
484544
}

0 commit comments

Comments
 (0)