// connectedAPIRequestHandler implements the "connected" API request. // Clients make the connected request once a tunnel connection has been // established and at least once per day. The last_connected input value, // which should be a connected_timestamp output from a previous connected // response, is used to calculate unique user stats. func connectedAPIRequestHandler( support *SupportServices, geoIPData GeoIPData, params requestJSONObject) ([]byte, error) { err := validateRequestParams(support, params, connectedRequestParams) if err != nil { return nil, common.ContextError(err) } log.LogRawFieldsWithTimestamp( getRequestLogFields( support, "connected", geoIPData, params, connectedRequestParams)) connectedResponse := common.ConnectedResponse{ ConnectedTimestamp: common.TruncateTimestampToHour(common.GetCurrentTimestamp()), } responsePayload, err := json.Marshal(connectedResponse) if err != nil { return nil, common.ContextError(err) } return responsePayload, nil }
// tunneledLookupIP resolves a split tunnel candidate hostname with a tunneled // DNS request. func tunneledLookupIP( dnsServerAddress string, dnsTunneler Tunneler, host string) (addr net.IP, ttl time.Duration, err error) { ipAddr := net.ParseIP(host) if ipAddr != nil { // maxDuration from golang.org/src/time/time.go return ipAddr, time.Duration(1<<63 - 1), nil } // dnsServerAddress must be an IP address ipAddr = net.ParseIP(dnsServerAddress) if ipAddr == nil { return nil, 0, common.ContextError(errors.New("invalid IP address")) } // Dial's alwaysTunnel is set to true to ensure this connection // is tunneled (also ensures this code path isn't circular). // Assumes tunnel dialer conn configures timeouts and interruptibility. conn, err := dnsTunneler.Dial(fmt.Sprintf( "%s:%d", dnsServerAddress, DNS_PORT), true, nil) if err != nil { return nil, 0, common.ContextError(err) } ipAddrs, ttls, err := ResolveIP(host, conn) if err != nil { return nil, 0, common.ContextError(err) } if len(ipAddrs) < 1 { return nil, 0, common.ContextError(errors.New("no IP address")) } return ipAddrs[0], ttls[0], nil }
// Encrypt plaintext with AES in CBC mode. func encryptAESCBC(plaintext []byte) ([]byte, []byte, []byte, error) { // CBC mode works on blocks so plaintexts need to be padded to the // next whole block (https://tools.ietf.org/html/rfc5246#section-6.2.3.2). plaintext = AddPKCS7Padding(plaintext, aes.BlockSize) ciphertext := make([]byte, len(plaintext)) iv, err := common.MakeSecureRandomBytes(aes.BlockSize) if err != nil { return nil, nil, nil, err } key, err := common.MakeSecureRandomBytes(aes.BlockSize) if err != nil { return nil, nil, nil, common.ContextError(err) } block, err := aes.NewCipher(key) if err != nil { return nil, nil, nil, common.ContextError(err) } mode := cipher.NewCBCEncrypter(block, iv) mode.CryptBlocks(ciphertext, plaintext) return iv, key, ciphertext, nil }
// NewServerContext makes the tunnelled handshake request to the Psiphon server // and returns a ServerContext struct for use with subsequent Psiphon server API // requests (e.g., periodic connected and status requests). func NewServerContext(tunnel *Tunnel, sessionId string) (*ServerContext, error) { // For legacy servers, set up psiphonHttpsClient for // accessing the Psiphon API via the web service. var psiphonHttpsClient *http.Client if !tunnel.serverEntry.SupportsSSHAPIRequests() || tunnel.config.TargetApiProtocol == common.PSIPHON_WEB_API_PROTOCOL { var err error psiphonHttpsClient, err = makePsiphonHttpsClient(tunnel) if err != nil { return nil, common.ContextError(err) } } serverContext := &ServerContext{ sessionId: sessionId, tunnelNumber: atomic.AddInt64(&nextTunnelNumber, 1), tunnel: tunnel, psiphonHttpsClient: psiphonHttpsClient, } err := serverContext.doHandshakeRequest() if err != nil { return nil, common.ContextError(err) } return serverContext, nil }
func HandleOSLRequest( tunnelOwner TunnelOwner, tunnel *Tunnel, payload []byte) error { var oslRequest protocol.OSLRequest err := json.Unmarshal(payload, &oslRequest) if err != nil { return common.ContextError(err) } if oslRequest.ClearLocalSLOKs { DeleteSLOKs() } seededNewSLOK := false for _, slok := range oslRequest.SeedPayload.SLOKs { duplicate, err := SetSLOK(slok.ID, slok.Key) if err != nil { // TODO: return error to trigger retry? NoticeAlert("SetSLOK failed: %s", common.ContextError(err)) } else if !duplicate { seededNewSLOK = true } if tunnel.config.EmitSLOKs { NoticeSLOKSeeded(base64.StdEncoding.EncodeToString(slok.ID), duplicate) } } if seededNewSLOK { tunnelOwner.SignalSeededNewSLOK() } return nil }
func parseResolveConf(filename string) (net.IP, error) { file, err := os.Open(filename) if err != nil { return nil, common.ContextError(err) } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") { continue } fields := strings.Fields(line) if len(fields) == 2 && fields[0] == "nameserver" { // TODO: parseResolverAddress will fail when the nameserver // is not an IP address. It may be a domain name. To support // this case, should proceed to the next "nameserver" line. return parseResolver(fields[1]) } } if err := scanner.Err(); err != nil { return nil, common.ContextError(err) } return nil, common.ContextError(errors.New("nameserver not found")) }
func validateRequestParams( support *SupportServices, params requestJSONObject, expectedParams []requestParamSpec) error { for _, expectedParam := range expectedParams { value := params[expectedParam.name] if value == nil { if expectedParam.flags&requestParamOptional != 0 { continue } return common.ContextError( fmt.Errorf("missing param: %s", expectedParam.name)) } var err error if expectedParam.flags&requestParamArray != 0 { err = validateStringArrayRequestParam(support, expectedParam, value) } else { err = validateStringRequestParam(support, expectedParam, value) } if err != nil { return common.ContextError(err) } } return nil }
// NewSupportServices initializes a new SupportServices. func NewSupportServices(config *Config) (*SupportServices, error) { trafficRulesSet, err := NewTrafficRulesSet(config.TrafficRulesFilename) if err != nil { return nil, common.ContextError(err) } psinetDatabase, err := psinet.NewDatabase(config.PsinetDatabaseFilename) if err != nil { return nil, common.ContextError(err) } geoIPService, err := NewGeoIPService( config.GeoIPDatabaseFilenames, config.DiscoveryValueHMACKey) if err != nil { return nil, common.ContextError(err) } dnsResolver, err := NewDNSResolver(config.DNSResolverIPAddress) if err != nil { return nil, common.ContextError(err) } return &SupportServices{ Config: config, TrafficRulesSet: trafficRulesSet, PsinetDatabase: psinetDatabase, GeoIPService: geoIPService, DNSResolver: dnsResolver, }, nil }
// unpackRemoteServerListFile reads a file that contains a // zlib compressed authenticated data package, validates // the package, and returns the payload. func unpackRemoteServerListFile( config *Config, filename string) (string, error) { fileReader, err := os.Open(filename) if err != nil { return "", common.ContextError(err) } defer fileReader.Close() zlibReader, err := zlib.NewReader(fileReader) if err != nil { return "", common.ContextError(err) } dataPackage, err := ioutil.ReadAll(zlibReader) zlibReader.Close() if err != nil { return "", common.ContextError(err) } payload, err := common.ReadAuthenticatedDataPackage( dataPackage, config.RemoteServerListSignaturePublicKey) if err != nil { return "", common.ContextError(err) } return payload, nil }
func scanServerEntries(scanner func(*protocol.ServerEntry)) error { err := singleton.db.View(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(serverEntriesBucket)) cursor := bucket.Cursor() for key, value := cursor.First(); key != nil; key, value = cursor.Next() { serverEntry := new(protocol.ServerEntry) err := json.Unmarshal(value, serverEntry) if err != nil { // In case of data corruption or a bug causing this condition, // do not stop iterating. NoticeAlert("scanServerEntries: %s", common.ContextError(err)) continue } scanner(serverEntry) } return nil }) if err != nil { return common.ContextError(err) } return nil }
// NewDatabase initializes a Database, calling Reload on the specified // filename. func NewDatabase(filename string) (*Database, error) { database := &Database{} database.ReloadableFile = common.NewReloadableFile( filename, func(filename string) error { psinetJSON, err := ioutil.ReadFile(filename) if err != nil { // On error, state remains the same return common.ContextError(err) } err = json.Unmarshal(psinetJSON, &database) if err != nil { // On error, state remains the same // (Unmarshal first validates the provided // JOSN and then populates the interface) return common.ContextError(err) } return nil }) _, err := database.Reload() if err != nil { return nil, common.ContextError(err) } return database, nil }
// InitLogging configures a logger according to the specified // config params. If not called, the default logger set by the // package init() is used. // Concurrenty note: should only be called from the main // goroutine. func InitLogging(config *Config) error { level, err := logrus.ParseLevel(config.LogLevel) if err != nil { return common.ContextError(err) } logWriter := os.Stderr if config.LogFilename != "" { logWriter, err = os.OpenFile( config.LogFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) if err != nil { return common.ContextError(err) } } log = &ContextLogger{ &logrus.Logger{ Out: logWriter, Formatter: &CustomJSONFormatter{}, Level: level, }, } return nil }
func readSshPacket( conn net.Conn, deobfuscate func([]byte)) ([]byte, bool, error) { prefix := make([]byte, SSH_PACKET_PREFIX_LENGTH) _, err := io.ReadFull(conn, prefix) if err != nil { return nil, false, common.ContextError(err) } deobfuscate(prefix) packetLength, _, payloadLength, messageLength := getSshPacketPrefix(prefix) if packetLength > SSH_MAX_PACKET_LENGTH { return nil, false, common.ContextError(errors.New("ssh packet length too large")) } readBuffer := make([]byte, messageLength) copy(readBuffer, prefix) _, err = io.ReadFull(conn, readBuffer[len(prefix):]) if err != nil { return nil, false, common.ContextError(err) } deobfuscate(readBuffer[len(prefix):]) isMsgNewKeys := false if payloadLength > 0 { packetType := int(readBuffer[SSH_PACKET_PREFIX_LENGTH]) if packetType == SSH_MSG_NEWKEYS { isMsgNewKeys = true } } return readBuffer, isMsgNewKeys, nil }
// NewClientObfuscator creates a new Obfuscator, staging a seed message to be // sent to the server (by the caller) and initializing stream ciphers to // obfuscate data. func NewClientObfuscator( config *ObfuscatorConfig) (obfuscator *Obfuscator, err error) { seed, err := common.MakeSecureRandomBytes(OBFUSCATE_SEED_LENGTH) if err != nil { return nil, common.ContextError(err) } clientToServerCipher, serverToClientCipher, err := initObfuscatorCiphers(seed, config) if err != nil { return nil, common.ContextError(err) } maxPadding := OBFUSCATE_MAX_PADDING if config.MaxPadding > 0 { maxPadding = config.MaxPadding } seedMessage, err := makeSeedMessage(maxPadding, seed, clientToServerCipher) if err != nil { return nil, common.ContextError(err) } return &Obfuscator{ seedMessage: seedMessage, clientToServerCipher: clientToServerCipher, serverToClientCipher: serverToClientCipher}, nil }
// NewTrafficRulesSet initializes a TrafficRulesSet with // the rules data in the specified config file. func NewTrafficRulesSet(filename string) (*TrafficRulesSet, error) { set := &TrafficRulesSet{} set.ReloadableFile = common.NewReloadableFile( filename, func(filename string) error { configJSON, err := ioutil.ReadFile(filename) if err != nil { // On error, state remains the same return common.ContextError(err) } var newSet TrafficRulesSet err = json.Unmarshal(configJSON, &newSet) if err != nil { return common.ContextError(err) } err = newSet.Validate() if err != nil { return common.ContextError(err) } // Modify actual traffic rules only after validation set.DefaultRules = newSet.DefaultRules set.FilteredRules = newSet.FilteredRules return nil }) _, err := set.Reload() if err != nil { return nil, common.ContextError(err) } return set, nil }
func initObfuscatorCiphers( seed []byte, config *ObfuscatorConfig) (*rc4.Cipher, *rc4.Cipher, error) { clientToServerKey, err := deriveKey(seed, []byte(config.Keyword), []byte(OBFUSCATE_CLIENT_TO_SERVER_IV)) if err != nil { return nil, nil, common.ContextError(err) } serverToClientKey, err := deriveKey(seed, []byte(config.Keyword), []byte(OBFUSCATE_SERVER_TO_CLIENT_IV)) if err != nil { return nil, nil, common.ContextError(err) } clientToServerCipher, err := rc4.NewCipher(clientToServerKey) if err != nil { return nil, nil, common.ContextError(err) } serverToClientCipher, err := rc4.NewCipher(serverToClientKey) if err != nil { return nil, nil, common.ContextError(err) } return clientToServerCipher, serverToClientCipher, nil }
// Write writes data to the connection. // net.Conn Deadlines are ignored. net.Conn concurrency semantics are supported. func (meek *MeekConn) Write(buffer []byte) (n int, err error) { if meek.closed() { return 0, common.ContextError(errors.New("meek connection is closed")) } // Repeats until all n bytes are written n = len(buffer) for len(buffer) > 0 { // Block until there is capacity in the send buffer var sendBuffer *bytes.Buffer select { case sendBuffer = <-meek.emptySendBuffer: case sendBuffer = <-meek.partialSendBuffer: case <-meek.broadcastClosed: return 0, common.ContextError(errors.New("meek connection has closed")) } writeLen := MAX_SEND_PAYLOAD_LENGTH - sendBuffer.Len() if writeLen > 0 { if writeLen > len(buffer) { writeLen = len(buffer) } _, err = sendBuffer.Write(buffer[:writeLen]) buffer = buffer[writeLen:] } meek.replaceSendBuffer(sendBuffer) } return n, err }
// DecodeServerEntry extracts server entries from the encoding // used by remote server lists and Psiphon server handshake requests. // // The resulting ServerEntry.LocalSource is populated with serverEntrySource, // which should be one of SERVER_ENTRY_SOURCE_EMBEDDED, SERVER_ENTRY_SOURCE_REMOTE, // SERVER_ENTRY_SOURCE_DISCOVERY, SERVER_ENTRY_SOURCE_TARGET. // ServerEntry.LocalTimestamp is populated with the provided timestamp, which // should be a RFC 3339 formatted string. These local fields are stored with the // server entry and reported to the server as stats (a coarse granularity timestamp // is reported). func DecodeServerEntry( encodedServerEntry, timestamp, serverEntrySource string) (serverEntry *ServerEntry, err error) { hexDecodedServerEntry, err := hex.DecodeString(encodedServerEntry) if err != nil { return nil, common.ContextError(err) } // Skip past legacy format (4 space delimited fields) and just parse the JSON config fields := bytes.SplitN(hexDecodedServerEntry, []byte(" "), 5) if len(fields) != 5 { return nil, common.ContextError(errors.New("invalid encoded server entry")) } serverEntry = new(ServerEntry) err = json.Unmarshal(fields[4], &serverEntry) if err != nil { return nil, common.ContextError(err) } // NOTE: if the source JSON happens to have values in these fields, they get clobbered. serverEntry.LocalSource = serverEntrySource serverEntry.LocalTimestamp = timestamp return serverEntry, nil }
// Take in an interface name ("lo", "eth0", "any") passed from either // a config setting, by using the -listenInterface flag on client or // -interface flag on server from the command line and return the IP // address associated with it. // If no interface is provided use the default loopback interface (127.0.0.1). // If "any" is passed then listen on 0.0.0.0 for client (invalid with server) func GetInterfaceIPAddress(listenInterface string) (string, error) { var ip net.IP if listenInterface == "" { ip = net.ParseIP("127.0.0.1") return ip.String(), nil } else if listenInterface == "any" { ip = net.ParseIP("0.0.0.0") return ip.String(), nil } else { availableInterfaces, err := net.InterfaceByName(listenInterface) if err != nil { return "", common.ContextError(err) } addrs, err := availableInterfaces.Addrs() if err != nil { return "", common.ContextError(err) } for _, addr := range addrs { iptype := addr.(*net.IPNet) if iptype == nil { continue } // TODO: IPv6 support ip = iptype.IP.To4() if ip == nil { continue } return ip.String(), nil } } return "", common.ContextError(errors.New("Could not find IP address of specified interface")) }
// MakeTunneledHttpClient returns a net/http.Client which is // configured to use custom dialing features including tunneled // dialing and, optionally, UseTrustedCACertificatesForStockTLS. // Unlike MakeUntunneledHttpsClient and makePsiphonHttpsClient, // This http.Client uses stock TLS and no scheme transformation // hack is required. func MakeTunneledHttpClient( config *Config, tunnel *Tunnel, requestTimeout time.Duration) (*http.Client, error) { tunneledDialer := func(_, addr string) (conn net.Conn, err error) { return tunnel.sshClient.Dial("tcp", addr) } transport := &http.Transport{ Dial: tunneledDialer, } if config.UseTrustedCACertificatesForStockTLS { if config.TrustedCACertificatesFilename == "" { return nil, common.ContextError(errors.New( "UseTrustedCACertificatesForStockTLS requires TrustedCACertificatesFilename")) } rootCAs := x509.NewCertPool() certData, err := ioutil.ReadFile(config.TrustedCACertificatesFilename) if err != nil { return nil, common.ContextError(err) } rootCAs.AppendCertsFromPEM(certData) transport.TLSClientConfig = &tls.Config{RootCAs: rootCAs} } return &http.Client{ Transport: transport, Timeout: requestTimeout, }, nil }
// MakeDownloadHttpClient is a resusable helper that sets up a // http.Client for use either untunneled or through a tunnel. // See MakeUntunneledHttpsClient for a note about request URL // rewritting. func MakeDownloadHttpClient( config *Config, tunnel *Tunnel, untunneledDialConfig *DialConfig, requestUrl string, requestTimeout time.Duration) (*http.Client, string, error) { var httpClient *http.Client var err error if tunnel != nil { httpClient, err = MakeTunneledHttpClient(config, tunnel, requestTimeout) if err != nil { return nil, "", common.ContextError(err) } } else { httpClient, requestUrl, err = MakeUntunneledHttpsClient( untunneledDialConfig, nil, requestUrl, requestTimeout) if err != nil { return nil, "", common.ContextError(err) } } return httpClient, requestUrl, nil }
// shamirSplit is a helper wrapper for sss.Split func shamirSplit(secret []byte, total, threshold int) ([][]byte, error) { if !isValidShamirSplit(total, threshold) { return nil, common.ContextError(errors.New("invalid parameters")) } if threshold == 1 { // Special case: each share is simply the secret shares := make([][]byte, total) for i := 0; i < total; i++ { shares[i] = secret } return shares, nil } shareMap, err := sss.Split(byte(total), byte(threshold), secret) if err != nil { return nil, common.ContextError(err) } shares := make([][]byte, total) for i := 0; i < total; i++ { // Note: sss.Combine index starts at 1 shares[i] = shareMap[byte(i)+1] } return shares, nil }
// makeOSLFileSpec creates a random OSL file key, splits it according // the the scheme's key splits, and sets the OSL ID as its first SLOK // ID. The returned key is used to encrypt the OSL payload and then // discarded; the key may be reassembled using the data in the KeyShares // tree, given sufficient SLOKs. func makeOSLFileSpec( scheme *Scheme, propagationChannelID string, firstSLOKTime time.Time) ([]byte, *OSLFileSpec, error) { ref := &slokReference{ PropagationChannelID: propagationChannelID, SeedSpecID: string(scheme.SeedSpecs[0].ID), Time: firstSLOKTime, } firstSLOK := deriveSLOK(scheme, ref) oslID := firstSLOK.ID fileKey, err := common.MakeSecureRandomBytes(KEY_LENGTH_BYTES) if err != nil { return nil, nil, common.ContextError(err) } keyShares, err := divideKey( scheme, fileKey, scheme.SeedPeriodKeySplits, propagationChannelID, &firstSLOKTime) if err != nil { return nil, nil, common.ContextError(err) } fileSpec := &OSLFileSpec{ ID: oslID, KeyShares: keyShares, } return fileKey, fileSpec, nil }
// selectProtocol is a helper that picks the tunnel protocol func selectProtocol( config *Config, serverEntry *protocol.ServerEntry) (selectedProtocol string, err error) { // TODO: properly handle protocols (e.g. FRONTED-MEEK-OSSH) vs. capabilities (e.g., {FRONTED-MEEK, OSSH}) // for now, the code is simply assuming that MEEK capabilities imply OSSH capability. if config.TunnelProtocol != "" { if !serverEntry.SupportsProtocol(config.TunnelProtocol) { return "", common.ContextError(fmt.Errorf("server does not have required capability")) } selectedProtocol = config.TunnelProtocol } else { // Pick at random from the supported protocols. This ensures that we'll eventually // try all possible protocols. Depending on network configuration, it may be the // case that some protocol is only available through multi-capability servers, // and a simpler ranked preference of protocols could lead to that protocol never // being selected. candidateProtocols := serverEntry.GetSupportedProtocols() if len(candidateProtocols) == 0 { return "", common.ContextError(fmt.Errorf("server does not have any supported capabilities")) } index, err := common.MakeSecureRandomInt(len(candidateProtocols)) if err != nil { return "", common.ContextError(err) } selectedProtocol = candidateProtocols[index] } return selectedProtocol, nil }
// dispatchAPIRequestHandler is the common dispatch point for both // web and SSH API requests. func dispatchAPIRequestHandler( support *SupportServices, apiProtocol string, geoIPData GeoIPData, name string, params requestJSONObject) (response []byte, reterr error) { // Recover from and log any unexpected panics caused by user input // handling bugs. User inputs should be properly validated; this // mechanism is only a last resort to prevent the process from // terminating in the case of a bug. defer func() { if e := recover(); e != nil { reterr = common.ContextError( fmt.Errorf( "request handler panic: %s: %s", e, debug.Stack())) } }() switch name { case common.PSIPHON_API_HANDSHAKE_REQUEST_NAME: return handshakeAPIRequestHandler(support, apiProtocol, geoIPData, params) case common.PSIPHON_API_CONNECTED_REQUEST_NAME: return connectedAPIRequestHandler(support, geoIPData, params) case common.PSIPHON_API_STATUS_REQUEST_NAME: return statusAPIRequestHandler(support, geoIPData, params) case common.PSIPHON_API_CLIENT_VERIFICATION_REQUEST_NAME: return clientVerificationAPIRequestHandler(support, geoIPData, params) } return nil, common.ContextError(fmt.Errorf("invalid request name: %s", name)) }
// Next returns the next server entry, by rank, for a legacyServerEntryIterator. // Returns nil with no error when there is no next item. func (iterator *legacyServerEntryIterator) Next() (serverEntry *ServerEntry, err error) { defer func() { if err != nil { iterator.Close() } }() if !iterator.cursor.Next() { err = iterator.cursor.Err() if err != nil { return nil, common.ContextError(err) } // There is no next item return nil, nil } var data []byte err = iterator.cursor.Scan(&data) if err != nil { return nil, common.ContextError(err) } serverEntry = new(ServerEntry) err = json.Unmarshal(data, serverEntry) if err != nil { return nil, common.ContextError(err) } return MakeCompatibleServerEntry(serverEntry), nil }
// interruptibleTCPDial establishes a TCP network connection. A conn is added // to config.PendingConns before blocking on network I/O, which enables interruption. // The caller is responsible for removing an established conn from PendingConns. // An upstream proxy is used when specified. // // Note: do not to set a UpstreamProxyUrl in the config when using // NewTCPDialer as a custom dialer for NewProxyAuthTransport (or http.Transport // with a ProxyUrl), as that would result in double proxy chaining. // // Note: interruption does not actually cancel a connection in progress; it // stops waiting for the goroutine blocking on connect()/Dial. func interruptibleTCPDial(addr string, config *DialConfig) (*TCPConn, error) { // Buffers the first result; senders should discard results when // sending would block, as that means the first result is already set. conn := &TCPConn{dialResult: make(chan error, 1)} // Enable interruption if config.PendingConns != nil && !config.PendingConns.Add(conn) { return nil, common.ContextError(errors.New("pending connections already closed")) } // Call the blocking Connect() in a goroutine. ConnectTimeout is handled // in the platform-specific tcpDial helper function. // Note: since this goroutine may be left running after an interrupt, don't // call Notice() or perform other actions unexpected after a Controller stops. // The lifetime of the goroutine may depend on the host OS TCP connect timeout // when tcpDial, amoung other things, when makes a blocking syscall.Connect() // call. go func() { var netConn net.Conn var err error if config.UpstreamProxyUrl != "" { netConn, err = proxiedTcpDial(addr, config, conn.dialResult) } else { netConn, err = tcpDial(addr, config, conn.dialResult) } // Mutex is necessary for referencing conn.isClosed and conn.Conn as // TCPConn.Close may be called while this goroutine is running. conn.mutex.Lock() // If already interrupted, cleanup the net.Conn resource and discard. if conn.isClosed && netConn != nil { netConn.Close() conn.mutex.Unlock() return } conn.Conn = netConn conn.mutex.Unlock() select { case conn.dialResult <- err: default: } }() // Wait until Dial completes (or times out) or until interrupt err := <-conn.dialResult if err != nil { if config.PendingConns != nil { config.PendingConns.Remove(conn) } return nil, common.ContextError(err) } // TODO: now allow conn.dialResult to be garbage collected? return conn, nil }
// relay sends and receives tunneled traffic (payload). An HTTP request is // triggered when data is in the write queue or at a polling interval. // There's a geometric increase, up to a maximum, in the polling interval when // no data is exchanged. Only one HTTP request is in flight at a time. func (meek *MeekConn) relay() { // Note: meek.Close() calls here in relay() are made asynchronously // (using goroutines) since Close() will wait on this WaitGroup. defer meek.relayWaitGroup.Done() interval := MIN_POLL_INTERVAL timeout := time.NewTimer(interval) sendPayload := make([]byte, MAX_SEND_PAYLOAD_LENGTH) for { timeout.Reset(interval) // Block until there is payload to send or it is time to poll var sendBuffer *bytes.Buffer select { case sendBuffer = <-meek.partialSendBuffer: case sendBuffer = <-meek.fullSendBuffer: case <-timeout.C: // In the polling case, send an empty payload case <-meek.broadcastClosed: // TODO: timeout case may be selected when broadcastClosed is set? return } sendPayloadSize := 0 if sendBuffer != nil { var err error sendPayloadSize, err = sendBuffer.Read(sendPayload) meek.replaceSendBuffer(sendBuffer) if err != nil { NoticeAlert("%s", common.ContextError(err)) go meek.Close() return } } receivedPayload, err := meek.roundTrip(sendPayload[:sendPayloadSize]) if err != nil { NoticeAlert("%s", common.ContextError(err)) go meek.Close() return } if receivedPayload == nil { // In this case, meek.roundTrip encountered broadcastClosed. Exit without error. return } receivedPayloadSize, err := meek.readPayload(receivedPayload) if err != nil { NoticeAlert("%s", common.ContextError(err)) go meek.Close() return } if receivedPayloadSize > 0 || sendPayloadSize > 0 { interval = 0 } else if interval == 0 { interval = MIN_POLL_INTERVAL } else { interval = time.Duration(float64(interval) * POLL_INTERNAL_MULTIPLIER) if interval >= MAX_POLL_INTERVAL { interval = MAX_POLL_INTERVAL } } } }
func getInt64RequestParam(params requestJSONObject, name string) (int64, error) { if params[name] == nil { return 0, common.ContextError(fmt.Errorf("missing param: %s", name)) } value, ok := params[name].(float64) if !ok { return 0, common.ContextError(fmt.Errorf("invalid param: %s", name)) } return int64(value), nil }
func verifyLegacyCertificate(conn *tls.Conn, expectedCertificate *x509.Certificate) error { certs := conn.ConnectionState().PeerCertificates if len(certs) < 1 { return common.ContextError(errors.New("no certificate to verify")) } if !bytes.Equal(certs[0].Raw, expectedCertificate.Raw) { return common.ContextError(errors.New("unexpected certificate")) } return nil }