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(&notice, "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(&notice, " [%s] %d", delay, bytesWritten)
   366  			} else if iterations == MAX_FRAGMENTOR_ITERATIONS_PER_NOTICE {
   367  				fmt.Fprintf(&notice, "...")
   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  }