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 }