github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/utils.go (about)

     1  /*
     2   * Copyright (c) 2015, 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 psiphon
    21  
    22  import (
    23  	"crypto/x509"
    24  	"encoding/base64"
    25  	std_errors "errors"
    26  	"fmt"
    27  	"net"
    28  	"os"
    29  	"runtime"
    30  	"runtime/debug"
    31  	"syscall"
    32  	"time"
    33  
    34  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
    35  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/crypto/ssh"
    36  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
    37  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/quic"
    38  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/refraction"
    39  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/resolver"
    40  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/stacktrace"
    41  )
    42  
    43  // MakePsiphonUserAgent constructs a User-Agent value to use for web service
    44  // requests made by the tunnel-core client. The User-Agent includes useful stats
    45  // information; it is to be used only for HTTPS requests, where the header
    46  // cannot be seen by an adversary.
    47  func MakePsiphonUserAgent(config *Config) string {
    48  	userAgent := "psiphon-tunnel-core"
    49  	if config.ClientVersion != "" {
    50  		userAgent += fmt.Sprintf("/%s", config.ClientVersion)
    51  	}
    52  	if config.ClientPlatform != "" {
    53  		userAgent += fmt.Sprintf(" (%s)", config.ClientPlatform)
    54  	}
    55  	return userAgent
    56  }
    57  
    58  func DecodeCertificate(encodedCertificate string) (certificate *x509.Certificate, err error) {
    59  	derEncodedCertificate, err := base64.StdEncoding.DecodeString(encodedCertificate)
    60  	if err != nil {
    61  		return nil, errors.Trace(err)
    62  	}
    63  	certificate, err = x509.ParseCertificate(derEncodedCertificate)
    64  	if err != nil {
    65  		return nil, errors.Trace(err)
    66  	}
    67  	return certificate, nil
    68  }
    69  
    70  // TrimError removes the middle of over-long error message strings
    71  func TrimError(err error) error {
    72  	const MAX_LEN = 100
    73  	message := fmt.Sprintf("%s", err)
    74  	if len(message) > MAX_LEN {
    75  		return std_errors.New(message[:MAX_LEN/2] + "..." + message[len(message)-MAX_LEN/2:])
    76  	}
    77  	return err
    78  }
    79  
    80  // IsAddressInUseError returns true when the err is due to EADDRINUSE/WSAEADDRINUSE.
    81  func IsAddressInUseError(err error) bool {
    82  	if err, ok := err.(*net.OpError); ok {
    83  		if err, ok := err.Err.(*os.SyscallError); ok {
    84  			if err.Err == syscall.EADDRINUSE {
    85  				return true
    86  			}
    87  			// Special case for Windows, WSAEADDRINUSE (10048). In the case
    88  			// where the socket already bound to the port has set
    89  			// SO_EXCLUSIVEADDRUSE, the error will instead be WSAEACCES (10013).
    90  			if errno, ok := err.Err.(syscall.Errno); ok {
    91  				if int(errno) == 10048 || int(errno) == 10013 {
    92  					return true
    93  				}
    94  			}
    95  		}
    96  	}
    97  	return false
    98  }
    99  
   100  // SyncFileWriter wraps a file and exposes an io.Writer. At predefined
   101  // steps, the file is synced (flushed to disk) while writing.
   102  type SyncFileWriter struct {
   103  	file  *os.File
   104  	step  int
   105  	count int
   106  }
   107  
   108  // NewSyncFileWriter creates a SyncFileWriter.
   109  func NewSyncFileWriter(file *os.File) *SyncFileWriter {
   110  	return &SyncFileWriter{
   111  		file:  file,
   112  		step:  2 << 16,
   113  		count: 0}
   114  }
   115  
   116  // Write implements io.Writer with periodic file syncing.
   117  func (writer *SyncFileWriter) Write(p []byte) (n int, err error) {
   118  	n, err = writer.file.Write(p)
   119  	if err != nil {
   120  		return
   121  	}
   122  	writer.count += n
   123  	if writer.count >= writer.step {
   124  		err = writer.file.Sync()
   125  		writer.count = 0
   126  	}
   127  	return
   128  }
   129  
   130  // emptyAddr implements the net.Addr interface. emptyAddr is intended to be
   131  // used as a stub, when a net.Addr is required but not used.
   132  type emptyAddr struct {
   133  }
   134  
   135  func (e *emptyAddr) String() string {
   136  	return ""
   137  }
   138  
   139  func (e *emptyAddr) Network() string {
   140  	return ""
   141  }
   142  
   143  // channelConn implements the net.Conn interface. channelConn allows use of
   144  // SSH.Channels in contexts where a net.Conn is expected. Only Read/Write/Close
   145  // are implemented and the remaining functions are stubs and expected to not
   146  // be used.
   147  type channelConn struct {
   148  	ssh.Channel
   149  }
   150  
   151  func newChannelConn(channel ssh.Channel) *channelConn {
   152  	return &channelConn{
   153  		Channel: channel,
   154  	}
   155  }
   156  
   157  func (conn *channelConn) LocalAddr() net.Addr {
   158  	return new(emptyAddr)
   159  }
   160  
   161  func (conn *channelConn) RemoteAddr() net.Addr {
   162  	return new(emptyAddr)
   163  }
   164  
   165  func (conn *channelConn) SetDeadline(_ time.Time) error {
   166  	return errors.TraceNew("unsupported")
   167  }
   168  
   169  func (conn *channelConn) SetReadDeadline(_ time.Time) error {
   170  	return errors.TraceNew("unsupported")
   171  }
   172  
   173  func (conn *channelConn) SetWriteDeadline(_ time.Time) error {
   174  	return errors.TraceNew("unsupported")
   175  }
   176  
   177  func emitMemoryMetrics() {
   178  	var memStats runtime.MemStats
   179  	runtime.ReadMemStats(&memStats)
   180  	NoticeInfo("Memory metrics at %s: goroutines %d | objects %d | alloc %s | inuse %s | sys %s | cumulative %d %s",
   181  		stacktrace.GetParentFunctionName(),
   182  		runtime.NumGoroutine(),
   183  		memStats.HeapObjects,
   184  		common.FormatByteCount(memStats.HeapAlloc),
   185  		common.FormatByteCount(memStats.HeapInuse+memStats.StackInuse+memStats.MSpanInuse+memStats.MCacheInuse),
   186  		common.FormatByteCount(memStats.Sys),
   187  		memStats.Mallocs,
   188  		common.FormatByteCount(memStats.TotalAlloc))
   189  }
   190  
   191  func emitDatastoreMetrics() {
   192  	NoticeInfo("Datastore metrics at %s: %s", stacktrace.GetParentFunctionName(), GetDataStoreMetrics())
   193  }
   194  
   195  func emitDNSMetrics(resolver *resolver.Resolver) {
   196  	NoticeInfo("DNS metrics at %s: %s", stacktrace.GetParentFunctionName(), resolver.GetMetrics())
   197  }
   198  
   199  func DoGarbageCollection() {
   200  	debug.SetGCPercent(5)
   201  	debug.FreeOSMemory()
   202  }
   203  
   204  // conditionallyEnabledComponents implements the
   205  // protocol.ConditionallyEnabledComponents interface.
   206  type conditionallyEnabledComponents struct {
   207  }
   208  
   209  func (c conditionallyEnabledComponents) QUICEnabled() bool {
   210  	return quic.Enabled()
   211  }
   212  
   213  func (c conditionallyEnabledComponents) RefractionNetworkingEnabled() bool {
   214  	return refraction.Enabled()
   215  }
   216  
   217  // FileMigration represents the action of moving a file, or directory, to a new
   218  // location.
   219  type FileMigration struct {
   220  
   221  	// Name is the name of the migration for logging because file paths are not
   222  	// logged as they may contain sensitive information.
   223  	Name string
   224  
   225  	// OldPath is the current location of the file.
   226  	OldPath string
   227  
   228  	// NewPath is the location that the file should be moved to.
   229  	NewPath string
   230  
   231  	// IsDir should be set to true if the file is a directory.
   232  	IsDir bool
   233  }
   234  
   235  // DoFileMigration performs the specified file move operation. An error will be
   236  // returned and the operation will not performed if: a file is expected, but a
   237  // directory is found; a directory is expected, but a file is found; or a file,
   238  // or directory, already exists at the target path of the move operation.
   239  // Note: an attempt is made to redact any file paths from the returned error.
   240  func DoFileMigration(migration FileMigration) error {
   241  
   242  	// Prefix string added to any errors for debug purposes.
   243  	errPrefix := ""
   244  	if len(migration.Name) > 0 {
   245  		errPrefix = fmt.Sprintf("(%s) ", migration.Name)
   246  	}
   247  
   248  	if !common.FileExists(migration.OldPath) {
   249  		return errors.TraceNew(errPrefix + "old path does not exist")
   250  	}
   251  	info, err := os.Stat(migration.OldPath)
   252  	if err != nil {
   253  		return errors.Tracef(errPrefix+"error getting file info: %s", common.RedactFilePathsError(err, migration.OldPath))
   254  	}
   255  	if info.IsDir() != migration.IsDir {
   256  		if migration.IsDir {
   257  			return errors.TraceNew(errPrefix + "expected directory but found file")
   258  		}
   259  
   260  		return errors.TraceNew(errPrefix + "expected but found directory")
   261  	}
   262  
   263  	if common.FileExists(migration.NewPath) {
   264  		return errors.TraceNew(errPrefix + "file already exists, will not overwrite")
   265  	}
   266  
   267  	err = os.Rename(migration.OldPath, migration.NewPath)
   268  	if err != nil {
   269  		return errors.Tracef(errPrefix+"renaming file failed with error %s", common.RedactFilePathsError(err, migration.OldPath, migration.NewPath))
   270  	}
   271  
   272  	return nil
   273  }