github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/fragmentor/fragmentor.go (about) 1 /* 2 * Copyright (c) 2018, 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 fragmentor 21 22 import ( 23 "bytes" 24 "context" 25 "fmt" 26 "net" 27 "sync" 28 "sync/atomic" 29 "time" 30 31 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common" 32 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors" 33 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters" 34 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng" 35 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol" 36 ) 37 38 const ( 39 MAX_FRAGMENTOR_NOTICES = 3 40 MAX_FRAGMENTOR_ITERATIONS_PER_NOTICE = 5 41 ) 42 43 // Config specifies a fragmentor configuration. NewUpstreamConfig and 44 // NewDownstreamConfig will generate configurations based on the given 45 // client parameters. 46 type Config struct { 47 isUpstream bool 48 probability float64 49 minTotalBytes int 50 maxTotalBytes int 51 minWriteBytes int 52 maxWriteBytes int 53 minDelay time.Duration 54 maxDelay time.Duration 55 fragmentPRNG *prng.PRNG 56 } 57 58 // NewUpstreamConfig creates a new Config; may return nil. Specifying the PRNG 59 // seed allows for optional replay of a fragmentor sequence. 60 func NewUpstreamConfig( 61 p parameters.ParametersAccessor, tunnelProtocol string, seed *prng.Seed) *Config { 62 return newConfig(p, true, tunnelProtocol, seed) 63 } 64 65 // NewDownstreamConfig creates a new Config; may return nil. Specifying the 66 // PRNG seed allows for optional replay of a fragmentor sequence. 67 func NewDownstreamConfig( 68 p parameters.ParametersAccessor, tunnelProtocol string, seed *prng.Seed) *Config { 69 return newConfig(p, false, tunnelProtocol, seed) 70 } 71 72 func newConfig( 73 p parameters.ParametersAccessor, 74 isUpstream bool, 75 tunnelProtocol string, 76 seed *prng.Seed) *Config { 77 78 if !protocol.TunnelProtocolIsCompatibleWithFragmentor(tunnelProtocol) { 79 return nil 80 } 81 82 probability := parameters.FragmentorProbability 83 limitProtocols := parameters.FragmentorLimitProtocols 84 minTotalBytes := parameters.FragmentorMinTotalBytes 85 maxTotalBytes := parameters.FragmentorMaxTotalBytes 86 minWriteBytes := parameters.FragmentorMinWriteBytes 87 maxWriteBytes := parameters.FragmentorMaxWriteBytes 88 minDelay := parameters.FragmentorMinDelay 89 maxDelay := parameters.FragmentorMaxDelay 90 91 if !isUpstream { 92 probability = parameters.FragmentorDownstreamProbability 93 limitProtocols = parameters.FragmentorDownstreamLimitProtocols 94 minTotalBytes = parameters.FragmentorDownstreamMinTotalBytes 95 maxTotalBytes = parameters.FragmentorDownstreamMaxTotalBytes 96 minWriteBytes = parameters.FragmentorDownstreamMinWriteBytes 97 maxWriteBytes = parameters.FragmentorDownstreamMaxWriteBytes 98 minDelay = parameters.FragmentorDownstreamMinDelay 99 maxDelay = parameters.FragmentorDownstreamMaxDelay 100 } 101 102 tunnelProtocols := p.TunnelProtocols(limitProtocols) 103 104 // When maxTotalBytes is 0 or the protocol is not a candidate for 105 // fragmentation, it's a certainty that no fragmentation will be 106 // performed. 107 // 108 // It's also possible that the weighted coin flip or random selection of 109 // bytesToFragment will result in no fragmentation. However, as "seed" may 110 // be nil, PRNG calls are deferred and these values are not yet known. 111 // 112 // TODO: when "seed" is not nil, the coin flip/range could be done here. 113 114 if p.Int(maxTotalBytes) == 0 || 115 (len(tunnelProtocols) > 0 && !common.Contains(tunnelProtocols, tunnelProtocol)) { 116 117 return nil 118 } 119 120 var fragmentPRNG *prng.PRNG 121 if seed != nil { 122 fragmentPRNG = prng.NewPRNGWithSeed(seed) 123 } 124 125 return &Config{ 126 isUpstream: isUpstream, 127 probability: p.Float(probability), 128 minTotalBytes: p.Int(minTotalBytes), 129 maxTotalBytes: p.Int(maxTotalBytes), 130 minWriteBytes: p.Int(minWriteBytes), 131 maxWriteBytes: p.Int(maxWriteBytes), 132 minDelay: p.Duration(minDelay), 133 maxDelay: p.Duration(maxDelay), 134 fragmentPRNG: fragmentPRNG, 135 } 136 } 137 138 // MayFragment indicates whether the fragmentor configuration may result in 139 // any fragmentation; config can be nil. When MayFragment is false, the caller 140 // should skip wrapping the associated conn with a fragmentor.Conn. 141 func (config *Config) MayFragment() bool { 142 return config != nil 143 } 144 145 // Conn implements simple fragmentation of application-level messages/packets 146 // into multiple TCP packets by splitting writes into smaller sizes and adding 147 // delays between writes. 148 // 149 // The intent of Conn is both to frustrate firewalls that perform DPI on 150 // application-level messages that cross TCP packets as well as to perform a 151 // simple size and timing transformation to the traffic shape of the initial 152 // portion of a TCP flow. 153 type Conn struct { 154 net.Conn 155 config *Config 156 noticeEmitter func(string) 157 runCtx context.Context 158 stopRunning context.CancelFunc 159 isClosed int32 160 writeMutex sync.Mutex 161 numNotices int 162 isReplay bool 163 fragmentPRNG *prng.PRNG 164 bytesToFragment int 165 bytesFragmented int 166 maxBytesWritten int 167 minBytesWritten int 168 minDelayed time.Duration 169 maxDelayed time.Duration 170 } 171 172 // NewConn creates a new Conn. When no seed was provided in the Config, 173 // SetReplay must be called before the first Write. 174 func NewConn( 175 config *Config, 176 noticeEmitter func(string), 177 conn net.Conn) *Conn { 178 179 runCtx, stopRunning := context.WithCancel(context.Background()) 180 return &Conn{ 181 Conn: conn, 182 config: config, 183 noticeEmitter: noticeEmitter, 184 runCtx: runCtx, 185 stopRunning: stopRunning, 186 fragmentPRNG: config.fragmentPRNG, 187 bytesToFragment: -1, 188 } 189 } 190 191 // GetMetrics implements the common.MetricsSource interface. 192 func (c *Conn) GetMetrics() common.LogFields { 193 c.writeMutex.Lock() 194 defer c.writeMutex.Unlock() 195 196 logFields := make(common.LogFields) 197 198 if c.bytesFragmented == 0 { 199 return logFields 200 } 201 202 var prefix string 203 if c.config.isUpstream { 204 prefix = "upstream_" 205 } else { 206 prefix = "downstream_" 207 } 208 209 logFields[prefix+"bytes_fragmented"] = c.bytesFragmented 210 logFields[prefix+"min_bytes_written"] = c.minBytesWritten 211 logFields[prefix+"max_bytes_written"] = c.maxBytesWritten 212 logFields[prefix+"min_delayed"] = int(c.minDelayed / time.Microsecond) 213 logFields[prefix+"max_delayed"] = int(c.maxDelayed / time.Microsecond) 214 215 return logFields 216 } 217 218 var upstreamMetricsNames = []string{ 219 "upstream_bytes_fragmented", 220 "upstream_min_bytes_written", 221 "upstream_max_bytes_written", 222 "upstream_min_delayed", 223 "upstream_max_delayed", 224 } 225 226 // GetUpstreamMetricsNames returns the upstream metrics parameter names. 227 func GetUpstreamMetricsNames() []string { 228 return upstreamMetricsNames 229 } 230 231 // SetReplay sets the PRNG to be used by the fragmentor, allowing for replay 232 // of a fragmentor sequence. SetReplay may be used to set the PRNG after a 233 // conn has already been wrapped with a fragmentor.Conn, when no PRNG is 234 // specified in the config, and before the first Write. SetReplay sets the 235 // fragmentor isReplay flag to true. 236 // 237 // For replay coordinated with a peer, SetReplay may be used with 238 // obfuscator.GetDerivedPRNG, using a seed provided by the peer. 239 // 240 // If no seed is specified in NewUp/DownstreamConfig and SetReplay is not 241 // called before the first Write, the Write will fail. If a seed was specified 242 // in the config, or SetReplay was already called, or the input PRNG is nil, 243 // SetReplay has no effect. 244 // 245 // SetReplay implements FragmentorReplayAccessor. 246 func (c *Conn) SetReplay(PRNG *prng.PRNG) { 247 248 c.writeMutex.Lock() 249 defer c.writeMutex.Unlock() 250 251 if c.fragmentPRNG == nil && PRNG != nil { 252 c.isReplay = true 253 c.fragmentPRNG = PRNG 254 } 255 } 256 257 // GetReplay returns the seed for the fragmentor PRNG, and whether the 258 // fragmentor was configured to replay. The seed return value may be nil when 259 // isReplay is false. 260 // 261 // GetReplay implements GetReplay. 262 func (c *Conn) GetReplay() (*prng.Seed, bool) { 263 264 c.writeMutex.Lock() 265 defer c.writeMutex.Unlock() 266 267 var seed *prng.Seed 268 269 if c.fragmentPRNG != nil { 270 seed = c.fragmentPRNG.GetSeed() 271 } 272 273 return seed, c.isReplay 274 } 275 276 func (c *Conn) Write(buffer []byte) (int, error) { 277 278 c.writeMutex.Lock() 279 defer c.writeMutex.Unlock() 280 281 if c.fragmentPRNG == nil { 282 return 0, errors.TraceNew("missing fragmentPRNG") 283 } 284 285 if c.bytesToFragment == -1 { 286 if !c.fragmentPRNG.FlipWeightedCoin(c.config.probability) { 287 c.bytesToFragment = 0 288 } else { 289 c.bytesToFragment = c.fragmentPRNG.Range( 290 c.config.minTotalBytes, c.config.maxTotalBytes) 291 } 292 } 293 294 if c.bytesFragmented >= c.bytesToFragment { 295 return c.Conn.Write(buffer) 296 } 297 298 totalBytesWritten := 0 299 300 emitNotice := c.noticeEmitter != nil && 301 c.numNotices < MAX_FRAGMENTOR_NOTICES 302 303 // TODO: use strings.Builder in Go 1.10 304 var notice bytes.Buffer 305 306 if emitNotice { 307 fmt.Fprintf(¬ice, "fragment %d bytes:", len(buffer)) 308 } 309 310 for iterations := 0; len(buffer) > 0; iterations += 1 { 311 312 delay := c.fragmentPRNG.Period(c.config.minDelay, c.config.maxDelay) 313 314 timer := time.NewTimer(delay) 315 316 var err error 317 select { 318 case <-c.runCtx.Done(): 319 err = c.runCtx.Err() 320 case <-timer.C: 321 } 322 timer.Stop() 323 324 if err != nil { 325 return totalBytesWritten, err 326 } 327 328 minWriteBytes := c.config.minWriteBytes 329 if minWriteBytes > len(buffer) { 330 minWriteBytes = len(buffer) 331 } 332 333 maxWriteBytes := c.config.maxWriteBytes 334 if maxWriteBytes > len(buffer) { 335 maxWriteBytes = len(buffer) 336 } 337 338 writeBytes := c.fragmentPRNG.Range(minWriteBytes, maxWriteBytes) 339 340 bytesWritten, err := c.Conn.Write(buffer[:writeBytes]) 341 342 totalBytesWritten += bytesWritten 343 c.bytesFragmented += bytesWritten 344 345 if err != nil { 346 return totalBytesWritten, err 347 } 348 349 if c.minBytesWritten == 0 || c.minBytesWritten > bytesWritten { 350 c.minBytesWritten = bytesWritten 351 } 352 if c.maxBytesWritten < bytesWritten { 353 c.maxBytesWritten = bytesWritten 354 } 355 356 if c.minDelayed == 0 || c.minDelayed > delay { 357 c.minDelayed = delay 358 } 359 if c.maxDelayed < delay { 360 c.maxDelayed = delay 361 } 362 363 if emitNotice { 364 if iterations < MAX_FRAGMENTOR_ITERATIONS_PER_NOTICE { 365 fmt.Fprintf(¬ice, " [%s] %d", delay, bytesWritten) 366 } else if iterations == MAX_FRAGMENTOR_ITERATIONS_PER_NOTICE { 367 fmt.Fprintf(¬ice, "...") 368 } 369 } 370 371 buffer = buffer[writeBytes:] 372 373 // As soon as bytesToFragment has been satisfied, don't fragment the 374 // remainder of this write buffer. 375 if c.bytesFragmented >= c.bytesToFragment { 376 bytesWritten, err := c.Conn.Write(buffer) 377 totalBytesWritten += bytesWritten 378 if err != nil { 379 return totalBytesWritten, err 380 } else { 381 buffer = nil 382 } 383 } 384 } 385 386 if emitNotice { 387 c.noticeEmitter(notice.String()) 388 c.numNotices += 1 389 } 390 391 return totalBytesWritten, nil 392 } 393 394 func (c *Conn) CloseWrite() error { 395 if closeWriter, ok := c.Conn.(common.CloseWriter); ok { 396 return closeWriter.CloseWrite() 397 } 398 return errors.TraceNew("underlying conn is not a CloseWriter") 399 } 400 401 func (c *Conn) Close() (err error) { 402 if !atomic.CompareAndSwapInt32(&c.isClosed, 0, 1) { 403 return nil 404 } 405 c.stopRunning() 406 return c.Conn.Close() 407 } 408 409 func (c *Conn) IsClosed() bool { 410 return atomic.LoadInt32(&c.isClosed) == 1 411 }