github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/server/replay.go (about) 1 /* 2 * Copyright (c) 2020, Psiphon Inc. 3 * All rights reserved. 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package server 21 22 import ( 23 "fmt" 24 "sync" 25 "time" 26 27 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors" 28 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters" 29 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng" 30 lrucache "github.com/cognusion/go-cache-lru" 31 ) 32 33 const ( 34 REPLAY_CACHE_MAX_ENTRIES = 100000 35 REPLAY_CACHE_CLEANUP_INTERVAL = 1 * time.Minute 36 ) 37 38 // ReplayCache is a cache of recently used and successful network obfuscation 39 // parameters that may be replayed -- reused -- for subsequent tunnel 40 // connections. 41 // 42 // Server-side replay is analogous to client-side replay, with one key 43 // difference: server-side replay can be applied across multiple clients in 44 // the same GeoIP scope. 45 // 46 // Replay is enabled with tactics, and tactics determine the tunnel quality 47 // targets for establishing and clearing replay parameters. 48 // 49 // ReplayCache has a maximum capacity with an LRU strategy to cap memory 50 // overhead. 51 type ReplayCache struct { 52 support *SupportServices 53 cacheMutex sync.Mutex 54 cache *lrucache.Cache 55 metrics *replayCacheMetrics 56 } 57 58 type replayCacheMetrics struct { 59 MaxCacheEntries int64 60 SetReplayCount int64 61 GetReplayHitCount int64 62 GetReplayMissCount int64 63 FailedReplayCount int64 64 DeleteReplayCount int64 65 } 66 67 type replayParameters struct { 68 replayPacketManipulation bool 69 packetManipulationSpecName string 70 replayFragmentor bool 71 fragmentorSeed *prng.Seed 72 failedCount int 73 } 74 75 // NewReplayCache creates a new ReplayCache. 76 func NewReplayCache(support *SupportServices) *ReplayCache { 77 // Cache TTL may vary based on tactics filtering, so each cache.Add must set 78 // the entry TTL. 79 return &ReplayCache{ 80 support: support, 81 cache: lrucache.NewWithLRU( 82 lrucache.NoExpiration, 83 REPLAY_CACHE_CLEANUP_INTERVAL, 84 REPLAY_CACHE_MAX_ENTRIES), 85 metrics: &replayCacheMetrics{}, 86 } 87 } 88 89 // Flush clears all entries in the ReplayCache. Flush should be called when 90 // tactics hot reload and change to clear any cached replay parameters that 91 // may be based on stale tactics. 92 func (r *ReplayCache) Flush() { 93 94 r.cacheMutex.Lock() 95 defer r.cacheMutex.Unlock() 96 97 r.cache.Flush() 98 } 99 100 // GetMetrics returns a snapshop of current ReplayCache event counters and 101 // resets all counters to zero. 102 func (r *ReplayCache) GetMetrics() LogFields { 103 104 r.cacheMutex.Lock() 105 defer r.cacheMutex.Unlock() 106 107 logFields := LogFields{ 108 "replay_max_cache_entries": r.metrics.MaxCacheEntries, 109 "replay_set_replay_count": r.metrics.SetReplayCount, 110 "replay_get_replay_hit_count": r.metrics.GetReplayHitCount, 111 "replay_get_replay_miss_count": r.metrics.GetReplayMissCount, 112 "replay_failed_replay_count": r.metrics.FailedReplayCount, 113 "replay_delete_replay_count": r.metrics.DeleteReplayCount, 114 } 115 116 r.metrics = &replayCacheMetrics{} 117 118 return logFields 119 } 120 121 // GetReplayTargetDuration returns the tactics replay target tunnel duration 122 // for the specified GeoIP data. Tunnels which are active for the specified 123 // duration are candidates for setting or extending replay parameters. Wait 124 // for the returned wait duration before evaluating the tunnel duration. Once 125 // this target is met, call SetReplayParameters, which will check additional 126 // targets and conditionally set replay parameters. 127 func (r *ReplayCache) GetReplayTargetDuration( 128 geoIPData GeoIPData) (bool, time.Duration, time.Duration) { 129 130 p, err := r.support.ServerTacticsParametersCache.Get(geoIPData) 131 if err != nil { 132 log.WithTraceFields(LogFields{"error": errors.Trace(err)}).Warning( 133 "ServerTacticsParametersCache.Get failed") 134 return false, 0, 0 135 } 136 137 if p.IsNil() { 138 // No tactics are configured; replay is disabled. 139 return false, 0, 0 140 } 141 142 if !p.Bool(parameters.ServerReplayUnknownGeoIP) && 143 geoIPData.Country == GEOIP_UNKNOWN_VALUE && 144 geoIPData.ASN == GEOIP_UNKNOWN_VALUE { 145 // Unless configured otherwise, skip replay for unknown GeoIP, since clients 146 // may not have equivilent network conditions. 147 return false, 0, 0 148 } 149 150 TTL := p.Duration(parameters.ServerReplayTTL) 151 152 if TTL == 0 { 153 // Server replay is disabled when TTL is 0. 154 return false, 0, 0 155 } 156 157 return true, 158 p.Duration(parameters.ServerReplayTargetWaitDuration), 159 p.Duration(parameters.ServerReplayTargetTunnelDuration) 160 } 161 162 // SetReplayParameters sets replay parameters, packetManipulationSpecName and 163 // fragmentorSeed, for the specified tunnel protocol and GeoIP scope. 164 // Once set, replay parameters are active for a tactics-configurable TTL. 165 // 166 // The specified tunneledBytesUp/Down must meet tactics replay bytes 167 // transferred targets. SetReplayParameters should be called only after first 168 // calling ReplayTargetDuration and ensuring the tunnel meets the active 169 // tunnel duration target. When cached replay parameters exist, their TTL is 170 // extended and any failure counts are reset to zero. 171 // 172 // SetReplayParameters must be called only once per tunnel. Extending replay 173 // parameters TTL should only be done only immediately after a successful 174 // tunnel dial and target achievement, as this is the part of a tunnel 175 // lifecycle at highest risk of blocking. 176 // 177 // The value pointed to by fragmentorSeed must not be mutated after calling 178 // SetReplayParameters. 179 func (r *ReplayCache) SetReplayParameters( 180 tunnelProtocol string, 181 geoIPData GeoIPData, 182 packetManipulationSpecName string, 183 fragmentorSeed *prng.Seed, 184 tunneledBytesUp int64, 185 tunneledBytesDown int64) { 186 187 p, err := r.support.ServerTacticsParametersCache.Get(geoIPData) 188 if err != nil { 189 log.WithTraceFields(LogFields{"error": errors.Trace(err)}).Warning( 190 "ServerTacticsParametersCache.Get failed") 191 return 192 } 193 194 if p.IsNil() { 195 // No tactics are configured; replay is disabled. 196 return 197 } 198 199 TTL := p.Duration(parameters.ServerReplayTTL) 200 201 if TTL == 0 { 202 return 203 } 204 205 targetUpstreamBytes := p.Int(parameters.ServerReplayTargetUpstreamBytes) 206 targetDownstreamBytes := p.Int(parameters.ServerReplayTargetDownstreamBytes) 207 208 if tunneledBytesUp < int64(targetUpstreamBytes) { 209 return 210 } 211 if tunneledBytesDown < int64(targetDownstreamBytes) { 212 return 213 } 214 215 key := r.makeKey(tunnelProtocol, geoIPData) 216 217 value := &replayParameters{} 218 219 if p.Bool(parameters.ServerReplayPacketManipulation) { 220 value.replayPacketManipulation = true 221 value.packetManipulationSpecName = packetManipulationSpecName 222 } 223 224 if p.Bool(parameters.ServerReplayFragmentor) { 225 value.replayFragmentor = (fragmentorSeed != nil) 226 value.fragmentorSeed = fragmentorSeed 227 } 228 229 r.cacheMutex.Lock() 230 defer r.cacheMutex.Unlock() 231 232 r.cache.Add(key, value, TTL) 233 234 // go-cache-lru is typically safe for concurrent access but explicit 235 // synchronization is required when accessing Items. Items may include 236 // entries that are expired but not yet purged. 237 cacheSize := int64(len(r.cache.Items())) 238 239 if cacheSize > r.metrics.MaxCacheEntries { 240 r.metrics.MaxCacheEntries = cacheSize 241 } 242 r.metrics.SetReplayCount += 1 243 } 244 245 // GetReplayPacketManipulation returns an active replay packet manipulation 246 // spec for the specified tunnel protocol and GeoIP scope. 247 // 248 // While Flush should be called to clear parameters based on stale tactics, 249 // it's still possible for GetReplayPacketManipulation to return a spec name 250 // that's no longer in the current list of known specs. 251 func (r *ReplayCache) GetReplayPacketManipulation( 252 tunnelProtocol string, 253 geoIPData GeoIPData) (string, bool) { 254 255 r.cacheMutex.Lock() 256 defer r.cacheMutex.Unlock() 257 258 parameters, ok := r.getReplayParameters( 259 tunnelProtocol, geoIPData) 260 if !ok { 261 return "", false 262 } 263 264 if !parameters.replayPacketManipulation { 265 return "", false 266 } 267 268 return parameters.packetManipulationSpecName, true 269 } 270 271 // GetReplayFragmentor returns an active replay fragmentor seed for the 272 // specified tunnel protocol and GeoIP scope. 273 func (r *ReplayCache) GetReplayFragmentor( 274 tunnelProtocol string, 275 geoIPData GeoIPData) (*prng.Seed, bool) { 276 277 r.cacheMutex.Lock() 278 defer r.cacheMutex.Unlock() 279 280 parameters, ok := r.getReplayParameters( 281 tunnelProtocol, geoIPData) 282 if !ok { 283 return nil, false 284 } 285 286 if !parameters.replayFragmentor { 287 return nil, false 288 } 289 290 return parameters.fragmentorSeed, true 291 } 292 293 func (r *ReplayCache) getReplayParameters( 294 tunnelProtocol string, 295 geoIPData GeoIPData) (*replayParameters, bool) { 296 297 key := r.makeKey(tunnelProtocol, geoIPData) 298 299 value, ok := r.cache.Get(key) 300 301 if !ok { 302 r.metrics.GetReplayMissCount += 1 303 return nil, false 304 } 305 306 r.metrics.GetReplayHitCount += 1 307 308 parameters, ok := value.(*replayParameters) 309 310 return parameters, ok 311 } 312 313 // FailedReplayParameters increments the count of tunnels which failed to 314 // complete any liveness test and API handshake after using replay parameters. 315 // Once a failure threshold is reached, cached replay parameters are cleared. 316 // Call this function for tunnels which meet the failure criteria. 317 func (r *ReplayCache) FailedReplayParameters( 318 tunnelProtocol string, 319 geoIPData GeoIPData, 320 packetManipulationSpecName string, 321 fragmentorSeed *prng.Seed) { 322 323 p, err := r.support.ServerTacticsParametersCache.Get(geoIPData) 324 if err != nil { 325 log.WithTraceFields(LogFields{"error": errors.Trace(err)}).Warning( 326 "ServerTacticsParametersCache.Get failed") 327 return 328 } 329 330 thresholdFailedCount := p.Int(parameters.ServerReplayFailedCountThreshold) 331 332 key := r.makeKey(tunnelProtocol, geoIPData) 333 334 r.cacheMutex.Lock() 335 defer r.cacheMutex.Unlock() 336 337 parameters, ok := r.getReplayParameters(tunnelProtocol, geoIPData) 338 if !ok { 339 return 340 } 341 342 // Do not count the failure if the replay values for the tunnel protocol and 343 // GeoIP scope are now different; these parameters now reflect a newer, 344 // successful tunnel. 345 346 if (parameters.replayPacketManipulation && 347 parameters.packetManipulationSpecName != packetManipulationSpecName) || 348 (parameters.replayFragmentor && 349 (fragmentorSeed == nil || 350 *parameters.fragmentorSeed != *fragmentorSeed)) { 351 return 352 } 353 354 parameters.failedCount += 1 355 r.metrics.FailedReplayCount += 1 356 357 if thresholdFailedCount == 0 { 358 // No failure limit; the entry will not be deleted. 359 return 360 } 361 362 if parameters.failedCount >= thresholdFailedCount { 363 r.cache.Delete(key) 364 r.metrics.DeleteReplayCount += 1 365 } 366 } 367 368 func (r *ReplayCache) makeKey( 369 tunnelProtocol string, geoIPData GeoIPData) string { 370 return fmt.Sprintf( 371 "%s-%s-%s", 372 tunnelProtocol, geoIPData.Country, geoIPData.ASN) 373 }