@@ -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