| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796 |
- /*
- * Copyright (c) 2015, Psiphon Inc.
- * All rights reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- package psiphon
- import (
- "bytes"
- "context"
- "crypto/rand"
- "crypto/tls"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "net"
- "net/http"
- "net/url"
- "strings"
- "sync"
- "sync/atomic"
- "time"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/obfuscator"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/quic"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/transforms"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/values"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/upstreamproxy"
- "golang.org/x/crypto/nacl/box"
- "golang.org/x/net/http2"
- )
- // MeekConn is based on meek-client.go from Tor:
- //
- // https://gitweb.torproject.org/pluggable-transports/meek.git/blob/HEAD:/meek-client/meek-client.go
- // CC0 1.0 Universal
- const (
- MEEK_PROTOCOL_VERSION = 3
- MEEK_MAX_REQUEST_PAYLOAD_LENGTH = 65536
- )
- type MeekMode int
- const (
- MeekModeRelay = iota
- MeekModeObfuscatedRoundTrip
- MeekModePlaintextRoundTrip
- )
- // MeekConfig specifies the behavior of a MeekConn.
- type MeekConfig struct {
- // DiagnosticID is the server ID to record in any diagnostics notices.
- DiagnosticID string
- // Parameters is the active set of parameters.Parameters to use
- // for the meek dial.
- Parameters *parameters.Parameters
- // Mode selects the mode of operation:
- //
- // MeekModeRelay: encapsulates net.Conn flows in HTTP requests and responses;
- // secures and obfuscates metadata in an encrypted HTTP cookie, making it
- // suitable for non-TLS HTTP and HTTPS with unverifed server certificates;
- // the caller is responsible for securing and obfuscating the net.Conn flows;
- // the origin server should be a meek server; used for the meek tunnel
- // protocols.
- //
- // MeekModeObfuscatedRoundTrip: enables ObfuscatedRoundTrip, which performs
- // HTTP round trips; secures and obfuscates metadata, including the end point
- // (or path), in an encrypted HTTP cookie, making it suitable for non-TLS
- // HTTP and HTTPS with unverifed server certificates; the caller is
- // responsible for securing and obfuscating request/response payloads; the
- // origin server should be a meek server; used for tactics requests.
- //
- // MeekModePlaintextRoundTrip: enables RoundTrip; the MeekConn is an
- // http.RoundTripper; there are no security or obfuscation measures at the
- // HTTP level; TLS and server certificate verification is required; the
- // origin server may be any HTTP(S) server.
- //
- // As with the other modes, MeekModePlaintextRoundTrip supports HTTP/2 with
- // utls, and integration with DialParameters for replay -- which are not
- // otherwise implemented if using just CustomTLSDialer and net.http.
- Mode MeekMode
- // DialAddress is the actual network address to dial to establish a
- // connection to the meek server. This may be either a fronted or
- // direct address. The address must be in the form "host:port",
- // where host may be a domain name or IP address.
- DialAddress string
- // UseQUIC indicates whether to use HTTP/2 over QUIC.
- UseQUIC bool
- // QUICVersion indicates which QUIC version to use.
- QUICVersion string
- // QUICClientHelloSeed is used for randomized QUIC Client Hellos.
- QUICClientHelloSeed *prng.Seed
- // QUICDisablePathMTUDiscovery indicates whether to disable path MTU
- // discovery in the QUIC client.
- QUICDisablePathMTUDiscovery bool
- // UseHTTPS indicates whether to use HTTPS (true) or HTTP (false).
- UseHTTPS bool
- // TLSProfile specifies the value for CustomTLSConfig.TLSProfile for all
- // underlying TLS connections created by this meek connection.
- TLSProfile string
- // TLSFragmentClientHello specifies whether to fragment the TLS Client Hello.
- TLSFragmentClientHello bool
- // LegacyPassthrough indicates that the server expects a legacy passthrough
- // message.
- LegacyPassthrough bool
- // NoDefaultTLSSessionID specifies the value for
- // CustomTLSConfig.NoDefaultTLSSessionID for all underlying TLS connections
- // created by this meek connection.
- NoDefaultTLSSessionID bool
- // RandomizedTLSProfileSeed specifies the value for
- // CustomTLSConfig.RandomizedTLSProfileSeed for all underlying TLS
- // connections created by this meek connection.
- RandomizedTLSProfileSeed *prng.Seed
- // UseObfuscatedSessionTickets indicates whether to use obfuscated session
- // tickets. Assumes UseHTTPS is true. Ignored for MeekModePlaintextRoundTrip.
- UseObfuscatedSessionTickets bool
- // SNIServerName is the value to place in the TLS/QUIC SNI server_name field
- // when HTTPS or QUIC is used.
- SNIServerName string
- // HostHeader is the value to place in the HTTP request Host header.
- HostHeader string
- // TransformedHostName records whether a hostname transformation is
- // in effect. This value is used for stats reporting.
- TransformedHostName bool
- // AddPsiphonFrontingHeader specifies whether to add the
- // X-Psiphon-Fronting-Address custom header.
- AddPsiphonFrontingHeader bool
- // VerifyServerName specifies a domain name that must appear in the server
- // certificate. When blank, server certificate verification is disabled.
- VerifyServerName string
- // VerifyPins specifies one or more certificate pin values, one of which must
- // appear in the verified server certificate chain. A pin value is the
- // base64-encoded SHA2 digest of a certificate's public key. When specified,
- // at least one pin must match at least one certificate in the chain, at any
- // position; e.g., the root CA may be pinned, or the server certificate,
- // etc.
- VerifyPins []string
- // DisableSystemRootCAs, when true, disables loading system root CAs when
- // verifying the server certificate chain. Set DisableSystemRootCAs only in
- // cases where system root CAs cannot be loaded and there is additional
- // security at the payload level; for example, if unsupported (iOS < 12) or
- // insufficient memory (VPN extension on iOS < 15).
- //
- // When DisableSystemRootCAs is set, both VerifyServerName and VerifyPins
- // must not be set.
- DisableSystemRootCAs bool
- // ClientTunnelProtocol is the protocol the client is using. It's included in
- // the meek cookie for optional use by the server, in cases where the server
- // cannot unambiguously determine the tunnel protocol. ClientTunnelProtocol
- // is used when selecting tactics targeted at specific protocols. Ignored for
- // MeekModePlaintextRoundTrip.
- ClientTunnelProtocol string
- // NetworkLatencyMultiplier specifies a custom network latency multiplier to
- // apply to client parameters used by this meek connection.
- NetworkLatencyMultiplier float64
- // The following values are used to create the obfuscated meek cookie.
- // Ignored for MeekModePlaintextRoundTrip.
- MeekCookieEncryptionPublicKey string
- MeekObfuscatedKey string
- MeekObfuscatorPaddingSeed *prng.Seed
- // HTTPTransformerParameters specifies an HTTP transformer to apply to the
- // meek connection if it uses HTTP.
- HTTPTransformerParameters *transforms.HTTPTransformerParameters
- // AdditionalHeaders is a set of additional arbitrary HTTP headers that
- // are added to all meek HTTP requests. An additional header is ignored
- // when the header name is already present in a meek request.
- AdditionalHeaders http.Header
- }
- // MeekConn is a network connection that tunnels net.Conn flows over HTTP and supports
- // "domain fronting". Meek sends client->server flow in HTTP request bodies and
- // receives server->client flow in HTTP response bodies. Polling is used to
- // approximate full duplex TCP. MeekConn also offers HTTP round trip modes.
- //
- // Domain fronting is a network obfuscation technique in which the connection to a web
- // server, typically a CDN, is indistinguishable from any other HTTPS
- // connection to the generic "fronting domain" -- the HTTP Host header is used
- // to route the requests to the actual destination. See
- // https://trac.torproject.org/projects/tor/wiki/doc/meek for more details.
- //
- // MeekConn also support unfronted operation, in which connections are made
- // without routing through a CDN; and plain HTTP operation, without TLS or
- // QUIC, with connection metadata obfuscated in HTTP cookies.
- type MeekConn struct {
- params *parameters.Parameters
- mode MeekMode
- networkLatencyMultiplier float64
- isQUIC bool
- url *url.URL
- additionalHeaders http.Header
- cookie *http.Cookie
- contentType string
- cookieSize int
- tlsPadding int
- limitRequestPayloadLength int
- redialTLSProbability float64
- underlyingDialer common.Dialer
- cachedTLSDialer *cachedTLSDialer
- transport transporter
- mutex sync.Mutex
- isClosed bool
- runCtx context.Context
- stopRunning context.CancelFunc
- relayWaitGroup *sync.WaitGroup
- firstUnderlyingConn net.Conn
- // For MeekModeObfuscatedRoundTrip
- meekCookieEncryptionPublicKey string
- meekObfuscatedKey string
- meekObfuscatorPaddingSeed *prng.Seed
- clientTunnelProtocol string
- // For MeekModeRelay
- fullReceiveBufferLength int
- readPayloadChunkLength int
- emptyReceiveBuffer chan *bytes.Buffer
- partialReceiveBuffer chan *bytes.Buffer
- fullReceiveBuffer chan *bytes.Buffer
- emptySendBuffer chan *bytes.Buffer
- partialSendBuffer chan *bytes.Buffer
- fullSendBuffer chan *bytes.Buffer
- }
- func (conn *MeekConn) getCustomParameters() parameters.ParametersAccessor {
- return conn.params.GetCustom(conn.networkLatencyMultiplier)
- }
- // transporter is implemented by both http.Transport and upstreamproxy.ProxyAuthTransport.
- type transporter interface {
- CloseIdleConnections()
- RoundTrip(req *http.Request) (resp *http.Response, err error)
- }
- // DialMeek returns an initialized meek connection. A meek connection is
- // an HTTP session which does not depend on an underlying socket connection (although
- // persistent HTTP connections are used for performance). This function may not
- // wait for the connection to be established before returning.
- func DialMeek(
- ctx context.Context,
- meekConfig *MeekConfig,
- dialConfig *DialConfig) (*MeekConn, error) {
- if meekConfig.UseQUIC && meekConfig.UseHTTPS {
- return nil, errors.TraceNew(
- "invalid config: only one of UseQUIC or UseHTTPS may be set")
- }
- if meekConfig.UseQUIC &&
- (meekConfig.VerifyServerName != "" || len(meekConfig.VerifyPins) > 0) {
- // TODO: UseQUIC VerifyServerName and VerifyPins support (required for MeekModePlaintextRoundTrip).
- return nil, errors.TraceNew(
- "invalid config: VerifyServerName and VerifyPins not supported for UseQUIC")
- }
- skipVerify := meekConfig.VerifyServerName == ""
- if len(meekConfig.VerifyPins) > 0 && skipVerify {
- return nil, errors.TraceNew(
- "invalid config: VerifyServerName must be set when VerifyPins is set")
- }
- if meekConfig.DisableSystemRootCAs &&
- (len(meekConfig.VerifyServerName) > 0 || len(meekConfig.VerifyPins) > 0) {
- return nil, errors.TraceNew(
- "invalid config: VerifyServerName and VerifyPins must not be set when DisableSystemRootCAs is set")
- }
- if meekConfig.Mode == MeekModePlaintextRoundTrip &&
- (!meekConfig.UseHTTPS || (skipVerify && !meekConfig.DisableSystemRootCAs)) {
- return nil, errors.TraceNew(
- "invalid config: MeekModePlaintextRoundTrip requires UseHTTPS and VerifyServerName when system root CAs can be loaded")
- }
- runCtx, stopRunning := context.WithCancel(context.Background())
- cleanupStopRunning := true
- cleanupCachedTLSDialer := true
- var cachedTLSDialer *cachedTLSDialer
- // Cleanup in error cases
- defer func() {
- if cleanupStopRunning {
- stopRunning()
- }
- if cleanupCachedTLSDialer && cachedTLSDialer != nil {
- cachedTLSDialer.close()
- }
- }()
- meek := &MeekConn{
- params: meekConfig.Parameters,
- mode: meekConfig.Mode,
- networkLatencyMultiplier: meekConfig.NetworkLatencyMultiplier,
- isClosed: false,
- runCtx: runCtx,
- stopRunning: stopRunning,
- relayWaitGroup: new(sync.WaitGroup),
- }
- if meek.mode == MeekModeRelay {
- var err error
- meek.cookie,
- meek.contentType,
- meek.tlsPadding,
- meek.limitRequestPayloadLength,
- meek.redialTLSProbability,
- err =
- makeMeekObfuscationValues(
- meek.getCustomParameters(),
- meekConfig.MeekCookieEncryptionPublicKey,
- meekConfig.MeekObfuscatedKey,
- meekConfig.MeekObfuscatorPaddingSeed,
- meekConfig.ClientTunnelProtocol,
- "")
- if err != nil {
- return nil, errors.Trace(err)
- }
- // For stats, record the size of the initial obfuscated cookie.
- meek.cookieSize = len(meek.cookie.Name) + len(meek.cookie.Value)
- }
- // Configure transport: QUIC or HTTPS or HTTP
- var (
- scheme string
- opaqueURL string
- transport transporter
- additionalHeaders http.Header
- proxyUrl func(*http.Request) (*url.URL, error)
- )
- if meekConfig.UseQUIC {
- meek.isQUIC = true
- scheme = "https"
- udpDialer := func(ctx context.Context) (net.PacketConn, *net.UDPAddr, error) {
- packetConn, remoteAddr, err := NewUDPConn(
- ctx, "udp", false, "", meekConfig.DialAddress, dialConfig)
- if err != nil {
- return nil, nil, errors.Trace(err)
- }
- return packetConn, remoteAddr, nil
- }
- var err error
- transport, err = quic.NewQUICTransporter(
- ctx,
- func(message string) {
- NoticeInfo(message)
- },
- udpDialer,
- meekConfig.SNIServerName,
- meekConfig.QUICVersion,
- meekConfig.QUICClientHelloSeed,
- meekConfig.QUICDisablePathMTUDiscovery)
- if err != nil {
- return nil, errors.Trace(err)
- }
- } else if meekConfig.UseHTTPS {
- // Custom TLS dialer:
- //
- // 1. ignores the HTTP request address and uses the fronting domain
- // 2. optionally disables SNI -- SNI breaks fronting when used with certain CDNs.
- // 3. may skip verifying the server cert.
- //
- // Reasoning for #3:
- //
- // With a TLS MiM attack in place, and server certs verified, we'll fail to connect because the client
- // will refuse to connect. That's not a successful outcome.
- //
- // With a MiM attack in place, and server certs not verified, we'll fail to connect if the MiM is actively
- // targeting Psiphon and classifying the HTTP traffic by Host header or payload signature.
- //
- // However, in the case of a passive MiM that's just recording traffic or an active MiM that's targeting
- // something other than Psiphon, the client will connect. This is a successful outcome.
- //
- // What is exposed to the MiM? The Host header does not contain a Psiphon server IP address, just an
- // unrelated, randomly generated domain name which cannot be used to block direct connections. The
- // Psiphon server IP is sent over meek, but it's in the encrypted cookie.
- //
- // The payload (user traffic) gets its confidentiality and integrity from the underlying SSH protocol.
- // So, nothing is leaked to the MiM apart from signatures which could be used to classify the traffic
- // as Psiphon to possibly block it; but note that not revealing that the client is Psiphon is outside
- // our threat model; we merely seek to evade mass blocking by taking steps that require progressively
- // more effort to block.
- //
- // There is a subtle attack remaining: an adversary that can MiM some CDNs but not others (and so can
- // classify Psiphon traffic on some CDNs but not others) may throttle non-MiM CDNs so that our server
- // selection always chooses tunnels to the MiM CDN (without any server cert verification, we won't
- // exclusively connect to non-MiM CDNs); then the adversary kills the underlying TCP connection after
- // some short period. This is partially mitigated by tactics mechanisms.
- scheme = "https"
- meek.initUnderlyingDialer(dialConfig)
- tlsConfig := &CustomTLSConfig{
- Parameters: meekConfig.Parameters,
- DialAddr: meekConfig.DialAddress,
- Dial: meek.underlyingDial,
- SNIServerName: meekConfig.SNIServerName,
- SkipVerify: skipVerify,
- VerifyServerName: meekConfig.VerifyServerName,
- VerifyPins: meekConfig.VerifyPins,
- DisableSystemRootCAs: meekConfig.DisableSystemRootCAs,
- TLSProfile: meekConfig.TLSProfile,
- NoDefaultTLSSessionID: &meekConfig.NoDefaultTLSSessionID,
- RandomizedTLSProfileSeed: meekConfig.RandomizedTLSProfileSeed,
- TLSPadding: meek.tlsPadding,
- TrustedCACertificatesFilename: dialConfig.TrustedCACertificatesFilename,
- FragmentClientHello: meekConfig.TLSFragmentClientHello,
- }
- tlsConfig.EnableClientSessionCache()
- if meekConfig.UseObfuscatedSessionTickets {
- tlsConfig.ObfuscatedSessionTicketKey = meekConfig.MeekObfuscatedKey
- }
- if meekConfig.Mode != MeekModePlaintextRoundTrip &&
- meekConfig.MeekObfuscatedKey != "" {
- // As the passthrough message is unique and indistinguishable from a normal
- // TLS client random value, we set it unconditionally and not just for
- // protocols which may support passthrough (even for those protocols,
- // clients don't know which servers are configured to use it).
- passthroughMessage, err := obfuscator.MakeTLSPassthroughMessage(
- !meekConfig.LegacyPassthrough,
- meekConfig.MeekObfuscatedKey)
- if err != nil {
- return nil, errors.Trace(err)
- }
- tlsConfig.PassthroughMessage = passthroughMessage
- }
- tlsDialer := NewCustomTLSDialer(tlsConfig)
- // Pre-dial one TLS connection in order to inspect the negotiated
- // application protocol. Then we create an HTTP/2 or HTTP/1.1 transport
- // depending on which protocol was negotiated. The TLS dialer
- // is assumed to negotiate only "h2" or "http/1.1"; or not negotiate
- // an application protocol.
- //
- // We cannot rely on net/http's HTTP/2 support since it's only
- // activated when http.Transport.DialTLS returns a golang crypto/tls.Conn;
- // e.g., https://github.com/golang/go/blob/c8aec4095e089ff6ac50d18e97c3f46561f14f48/src/net/http/transport.go#L1040
- //
- // The pre-dialed connection is stored in a cachedTLSDialer, which will
- // return the cached pre-dialed connection to its first Dial caller, and
- // use the tlsDialer for all other Dials.
- //
- // cachedTLSDialer.close() must be called on all exits paths from this
- // function and in meek.Close() to ensure the cached conn is closed in
- // any case where no Dial call is made.
- //
- // The pre-dial must be interruptible so that DialMeek doesn't block and
- // hang/delay a shutdown or end of establishment. So the pre-dial uses
- // the Controller's PendingConns, not the MeekConn PendingConns. For this
- // purpose, a special preDialer is configured.
- //
- // Only one pre-dial attempt is made; there are no retries. This differs
- // from relayRoundTrip, which retries and may redial for each retry.
- // Retries at the pre-dial phase are less useful since there's no active
- // session to preserve, and establishment will simply try another server.
- // Note that the underlying TCPDial may still try multiple IP addreses when
- // the destination is a domain and it resolves to multiple IP adresses.
- // The pre-dial is made within the parent dial context, so that DialMeek
- // may be interrupted. Subsequent dials are made within the meek round trip
- // request context. Since http.DialTLS doesn't take a context argument
- // (yet; as of Go 1.9 this issue is still open: https://github.com/golang/go/issues/21526),
- // cachedTLSDialer is used as a conduit to send the request context.
- // meekConn.relayRoundTrip sets its request context into cachedTLSDialer,
- // and cachedTLSDialer.dial uses that context.
- // As DialAddr is set in the CustomTLSConfig, no address is required here.
- preConn, err := tlsDialer(ctx, "tcp", "")
- if err != nil {
- return nil, errors.Trace(err)
- }
- cachedTLSDialer = newCachedTLSDialer(preConn, tlsDialer)
- if IsTLSConnUsingHTTP2(preConn) {
- NoticeInfo("negotiated HTTP/2 for %s", meekConfig.DiagnosticID)
- transport = &http2.Transport{
- DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) {
- return cachedTLSDialer.dial(network, addr)
- },
- }
- } else {
- transport = &http.Transport{
- DialTLS: func(network, addr string) (net.Conn, error) {
- return cachedTLSDialer.dial(network, addr)
- },
- }
- }
- } else {
- scheme = "http"
- var dialer common.Dialer
- // For HTTP, and when the meekConfig.DialAddress matches the
- // meekConfig.HostHeader, we let http.Transport handle proxying.
- // http.Transport will put the the HTTP server address in the HTTP
- // request line. In this one case, we can use an HTTP proxy that does
- // not offer CONNECT support.
- if strings.HasPrefix(dialConfig.UpstreamProxyURL, "http://") &&
- (meekConfig.DialAddress == meekConfig.HostHeader ||
- meekConfig.DialAddress == meekConfig.HostHeader+":80") {
- url, err := common.SafeParseURL(dialConfig.UpstreamProxyURL)
- if err != nil {
- return nil, errors.Trace(err)
- }
- proxyUrl = http.ProxyURL(url)
- // Here, the dialer must use the address that http.Transport
- // passes in (which will be proxy address).
- copyDialConfig := new(DialConfig)
- *copyDialConfig = *dialConfig
- copyDialConfig.UpstreamProxyURL = ""
- meek.initUnderlyingDialer(copyDialConfig)
- dialer = meek.underlyingDial
- // In this proxy case, the destination server address is in the
- // request line URL. net/http will render the request line using
- // the URL but preferring the Host header for the host value,
- // which means any custom host header will clobber the true
- // destination address. The URL.Opaque logic is applied in this
- // case, to force the request line URL value.
- //
- // This URL.Opaque setting assumes MeekModeRelay, with no path; at
- // this time plain HTTP is used only with MeekModeRelay.
- // x/net/http2 will reject requests where the URL.Opaque contains
- // more than the path; but HTTP/2 is not used in this case.
- values := dialConfig.CustomHeaders["Host"]
- if len(values) > 0 {
- opaqueURL = "http://" + meekConfig.DialAddress + "/"
- }
- } else {
- // If dialConfig.UpstreamProxyURL is set, HTTP proxying via
- // CONNECT will be used by the dialer.
- meek.initUnderlyingDialer(dialConfig)
- baseDialer := meek.underlyingDial
- // The dialer ignores any address that http.Transport will pass in
- // (derived from the HTTP request URL) and always dials
- // meekConfig.DialAddress.
- dialer = func(ctx context.Context, network, _ string) (net.Conn, error) {
- return baseDialer(ctx, network, meekConfig.DialAddress)
- }
- }
- if protocol.TunnelProtocolUsesMeekHTTP(meekConfig.ClientTunnelProtocol) {
- // Only apply transformer if it will perform a transform; otherwise
- // applying a no-op transform will incur an unnecessary performance
- // cost.
- if meekConfig.HTTPTransformerParameters != nil && meekConfig.HTTPTransformerParameters.ProtocolTransformSpec != nil {
- dialer = transforms.WrapDialerWithHTTPTransformer(dialer, meekConfig.HTTPTransformerParameters)
- }
- }
- httpTransport := &http.Transport{
- Proxy: proxyUrl,
- DialContext: dialer,
- }
- if proxyUrl != nil {
- // When http.Transport is handling proxying, wrap transport with a
- // transport that (a) adds custom headers; (b) can perform HTTP
- // proxy auth negotiation.
- var err error
- transport, err = upstreamproxy.NewProxyAuthTransport(
- httpTransport, dialConfig.CustomHeaders)
- if err != nil {
- return nil, errors.Trace(err)
- }
- } else {
- transport = httpTransport
- }
- }
- url := &url.URL{
- Scheme: scheme,
- Host: meekConfig.HostHeader,
- Path: "/",
- Opaque: opaqueURL,
- }
- if scheme == "http" && proxyUrl == nil {
- // Add custom headers to HTTP. This may be unproxied HTTP, or CONNECT
- // method proxied HTTP, which is handled implicitly by DialTCP (in the
- // latter case, the CONNECT request itself will also have custom
- // headers via upstreamproxy applied by the dialer).
- //
- // When proxyUrl != nil, proxying is handled by http.Transport and
- // custom headers are set in upstreamproxy.NewProxyAuthTransport, above.
- additionalHeaders = dialConfig.CustomHeaders
- } else {
- additionalHeaders = make(http.Header)
- // User-Agent is passed in via dialConfig.CustomHeaders. Always use
- // any User-Agent header, even when not using all custom headers.
- userAgent := dialConfig.CustomHeaders.Get("User-Agent")
- if userAgent != "" {
- additionalHeaders.Set("User-Agent", userAgent)
- }
- }
- if meekConfig.AddPsiphonFrontingHeader {
- host, _, err := net.SplitHostPort(meekConfig.DialAddress)
- if err != nil {
- return nil, errors.Trace(err)
- }
- additionalHeaders.Set("X-Psiphon-Fronting-Address", host)
- }
- if meekConfig.AdditionalHeaders != nil {
- for name, value := range meekConfig.AdditionalHeaders {
- if _, ok := additionalHeaders[name]; !ok {
- additionalHeaders[name] = value
- }
- }
- }
- meek.url = url
- meek.additionalHeaders = additionalHeaders
- meek.cachedTLSDialer = cachedTLSDialer
- meek.transport = transport
- // stopRunning and cachedTLSDialer will now be closed in meek.Close()
- cleanupStopRunning = false
- cleanupCachedTLSDialer = false
- // Allocate relay resources, including buffers and running the relay
- // go routine, only when running in relay mode.
- if meek.mode == MeekModeRelay {
- // The main loop of a MeekConn is run in the relay() goroutine.
- // A MeekConn implements net.Conn concurrency semantics:
- // "Multiple goroutines may invoke methods on a Conn simultaneously."
- //
- // Read() calls and relay() are synchronized by exchanging control of a single
- // receiveBuffer (bytes.Buffer). This single buffer may be:
- // - in the emptyReceiveBuffer channel when it is available and empty;
- // - in the partialReadBuffer channel when it is available and contains data;
- // - in the fullReadBuffer channel when it is available and full of data;
- // - "checked out" by relay or Read when they are are writing to or reading from the
- // buffer, respectively.
- // relay() will obtain the buffer from either the empty or partial channel but block when
- // the buffer is full. Read will obtain the buffer from the partial or full channel when
- // there is data to read but block when the buffer is empty.
- // Write() calls and relay() are synchronized in a similar way, using a single
- // sendBuffer.
- p := meek.getCustomParameters()
- if p.Bool(parameters.MeekLimitBufferSizes) {
- meek.fullReceiveBufferLength = p.Int(parameters.MeekLimitedFullReceiveBufferLength)
- meek.readPayloadChunkLength = p.Int(parameters.MeekLimitedReadPayloadChunkLength)
- } else {
- meek.fullReceiveBufferLength = p.Int(parameters.MeekFullReceiveBufferLength)
- meek.readPayloadChunkLength = p.Int(parameters.MeekReadPayloadChunkLength)
- }
- meek.emptyReceiveBuffer = make(chan *bytes.Buffer, 1)
- meek.partialReceiveBuffer = make(chan *bytes.Buffer, 1)
- meek.fullReceiveBuffer = make(chan *bytes.Buffer, 1)
- meek.emptySendBuffer = make(chan *bytes.Buffer, 1)
- meek.partialSendBuffer = make(chan *bytes.Buffer, 1)
- meek.fullSendBuffer = make(chan *bytes.Buffer, 1)
- meek.emptyReceiveBuffer <- new(bytes.Buffer)
- meek.emptySendBuffer <- new(bytes.Buffer)
- meek.relayWaitGroup.Add(1)
- go meek.relay()
- } else if meek.mode == MeekModeObfuscatedRoundTrip {
- meek.meekCookieEncryptionPublicKey = meekConfig.MeekCookieEncryptionPublicKey
- meek.meekObfuscatedKey = meekConfig.MeekObfuscatedKey
- meek.meekObfuscatorPaddingSeed = meekConfig.MeekObfuscatorPaddingSeed
- meek.clientTunnelProtocol = meekConfig.ClientTunnelProtocol
- } else if meek.mode == MeekModePlaintextRoundTrip {
- // MeekModeRelay and MeekModeObfuscatedRoundTrip set the Host header
- // implicitly via meek.url; MeekModePlaintextRoundTrip does not use
- // meek.url; it uses the RoundTrip input request.URL instead. So the
- // Host header is set to meekConfig.HostHeader explicitly here.
- meek.additionalHeaders.Add("Host", meekConfig.HostHeader)
- }
- return meek, nil
- }
- func (meek *MeekConn) initUnderlyingDialer(dialConfig *DialConfig) {
- // Not safe for concurrent calls; should be called only from DialMeek.
- meek.underlyingDialer = NewTCPDialer(dialConfig)
- }
- func (meek *MeekConn) underlyingDial(ctx context.Context, network, addr string) (net.Conn, error) {
- conn, err := meek.underlyingDialer(ctx, network, addr)
- if err == nil {
- meek.mutex.Lock()
- if meek.firstUnderlyingConn == nil {
- // Keep a reference to the first underlying conn to be used as a
- // common.MetricsSource in GetMetrics. This enables capturing
- // metrics such as fragmentor configuration.
- meek.firstUnderlyingConn = conn
- }
- meek.mutex.Unlock()
- }
- // Note: no trace error to preserve error type
- return conn, err
- }
- type cachedTLSDialer struct {
- usedCachedConn int32
- cachedConn net.Conn
- dialer common.Dialer
- mutex sync.Mutex
- requestCtx context.Context
- }
- func newCachedTLSDialer(cachedConn net.Conn, dialer common.Dialer) *cachedTLSDialer {
- return &cachedTLSDialer{
- cachedConn: cachedConn,
- dialer: dialer,
- }
- }
- func (c *cachedTLSDialer) setRequestContext(requestCtx context.Context) {
- // Note: not using sync.Value since underlying type of requestCtx may change.
- c.mutex.Lock()
- defer c.mutex.Unlock()
- c.requestCtx = requestCtx
- }
- func (c *cachedTLSDialer) dial(network, addr string) (net.Conn, error) {
- if atomic.CompareAndSwapInt32(&c.usedCachedConn, 0, 1) {
- conn := c.cachedConn
- c.cachedConn = nil
- return conn, nil
- }
- c.mutex.Lock()
- ctx := c.requestCtx
- c.mutex.Unlock()
- if ctx == nil {
- ctx = context.Background()
- }
- return c.dialer(ctx, network, addr)
- }
- func (c *cachedTLSDialer) close() {
- if atomic.CompareAndSwapInt32(&c.usedCachedConn, 0, 1) {
- c.cachedConn.Close()
- c.cachedConn = nil
- }
- }
- // Close terminates the meek connection and releases its resources. In in
- // MeekModeRelay, Close waits for the relay goroutine to stop.
- func (meek *MeekConn) Close() (err error) {
- // A mutex is required to support net.Conn concurrency semantics.
- meek.mutex.Lock()
- isClosed := meek.isClosed
- meek.isClosed = true
- meek.mutex.Unlock()
- if !isClosed {
- meek.stopRunning()
- if meek.cachedTLSDialer != nil {
- meek.cachedTLSDialer.close()
- }
- // stopRunning interrupts HTTP requests in progress by closing the context
- // associated with the request. In the case of h2quic.RoundTripper, testing
- // indicates that quic-go.receiveStream.readImpl in _not_ interrupted in
- // this case, and so an in-flight FRONTED-MEEK-QUIC round trip may hang shutdown
- // in relayRoundTrip->readPayload->...->quic-go.receiveStream.readImpl.
- //
- // To workaround this, we call CloseIdleConnections _before_ Wait, as, in
- // the case of QUICTransporter, this closes the underlying UDP sockets which
- // interrupts any blocking I/O calls.
- //
- // The standard CloseIdleConnections call _after_ wait is for the net/http
- // case: it only closes idle connections, so the call should be after wait.
- // This call is intended to clean up all network resources deterministically
- // before Close returns.
- if meek.isQUIC {
- meek.transport.CloseIdleConnections()
- }
- meek.relayWaitGroup.Wait()
- meek.transport.CloseIdleConnections()
- }
- return nil
- }
- // IsClosed implements the Closer interface. The return value
- // indicates whether the MeekConn has been closed.
- func (meek *MeekConn) IsClosed() bool {
- meek.mutex.Lock()
- isClosed := meek.isClosed
- meek.mutex.Unlock()
- return isClosed
- }
- // GetMetrics implements the common.MetricsSource interface.
- func (meek *MeekConn) GetMetrics() common.LogFields {
- logFields := make(common.LogFields)
- if meek.mode == MeekModeRelay {
- logFields["meek_cookie_size"] = meek.cookieSize
- logFields["meek_tls_padding"] = meek.tlsPadding
- logFields["meek_limit_request"] = meek.limitRequestPayloadLength
- logFields["meek_redial_probability"] = meek.redialTLSProbability
- }
- // Include metrics, such as fragmentor metrics, from the _first_ underlying
- // dial conn. Properties of subsequent underlying dial conns are not reflected
- // in these metrics; we assume that the first dial conn, which most likely
- // transits the various protocol handshakes, is most significant.
- meek.mutex.Lock()
- underlyingMetrics, ok := meek.firstUnderlyingConn.(common.MetricsSource)
- if ok {
- logFields.Add(underlyingMetrics.GetMetrics())
- }
- meek.mutex.Unlock()
- return logFields
- }
- // GetNoticeMetrics implements the common.NoticeMetricsSource interface.
- func (meek *MeekConn) GetNoticeMetrics() common.LogFields {
- // These fields are logged only in notices, for diagnostics. The server
- // will log the same values, but derives them from HTTP headers, so they
- // don't need to be sent in the API request.
- logFields := make(common.LogFields)
- logFields["meek_cookie_name"] = meek.cookie.Name
- logFields["meek_content_type"] = meek.contentType
- return logFields
- }
- // ObfuscatedRoundTrip makes a request to the meek server and returns the
- // response. A new, obfuscated meek cookie is created for every request. The
- // specified end point is recorded in the cookie and is not exposed as
- // plaintext in the meek traffic. The caller is responsible for securing and
- // obfuscating the request body.
- //
- // ObfuscatedRoundTrip is not safe for concurrent use. The caller must ensure
- // only one ObfuscatedRoundTrip call is active at once. If Close is called
- // before or concurrent with ObfuscatedRoundTrip, or before the response body
- // is read, idle connections may be left open.
- func (meek *MeekConn) ObfuscatedRoundTrip(
- requestCtx context.Context, endPoint string, requestBody []byte) ([]byte, error) {
- if meek.mode != MeekModeObfuscatedRoundTrip {
- return nil, errors.TraceNew("operation unsupported")
- }
- cookie, contentType, _, _, _, err := makeMeekObfuscationValues(
- meek.getCustomParameters(),
- meek.meekCookieEncryptionPublicKey,
- meek.meekObfuscatedKey,
- meek.meekObfuscatorPaddingSeed,
- meek.clientTunnelProtocol,
- endPoint)
- if err != nil {
- return nil, errors.Trace(err)
- }
- // Note:
- //
- // - multiple, concurrent ObfuscatedRoundTrip calls are unsafe due to the
- // setDialerRequestContext calls in newRequest.
- //
- // At this time, ObfuscatedRoundTrip is used for tactics in Controller and
- // the concurrency constraints are satisfied.
- request, err := meek.newRequest(
- requestCtx, cookie, contentType, bytes.NewReader(requestBody), 0)
- if err != nil {
- return nil, errors.Trace(err)
- }
- meek.scheduleQUICCloseIdle(request)
- response, err := meek.transport.RoundTrip(request)
- if err == nil {
- defer response.Body.Close()
- if response.StatusCode != http.StatusOK {
- err = fmt.Errorf("unexpected response status code: %d", response.StatusCode)
- }
- }
- if err != nil {
- return nil, errors.Trace(err)
- }
- responseBody, err := ioutil.ReadAll(response.Body)
- if err != nil {
- return nil, errors.Trace(err)
- }
- return responseBody, nil
- }
- // RoundTrip implements the http.RoundTripper interface. RoundTrip may only be
- // used when TLS and server certificate verification are configured. RoundTrip
- // does not implement any security or obfuscation at the HTTP layer.
- //
- // RoundTrip is not safe for concurrent use. The caller must ensure only one
- // RoundTrip call is active at once. If Close is called before or concurrent
- // with RoundTrip, or before the response body is read, idle connections may
- // be left open.
- func (meek *MeekConn) RoundTrip(request *http.Request) (*http.Response, error) {
- if meek.mode != MeekModePlaintextRoundTrip {
- return nil, errors.TraceNew("operation unsupported")
- }
- requestCtx := request.Context()
- // Clone the request to apply addtional headers without modifying the input.
- request = request.Clone(requestCtx)
- meek.addAdditionalHeaders(request)
- // The setDialerRequestContext/CloseIdleConnections concurrency note in
- // ObfuscatedRoundTrip applies to RoundTrip as well.
- // Ensure dials are made within the request context.
- meek.setDialerRequestContext(requestCtx)
- meek.scheduleQUICCloseIdle(request)
- response, err := meek.transport.RoundTrip(request)
- if err != nil {
- return nil, errors.Trace(err)
- }
- return response, nil
- }
- // Read reads data from the connection.
- // net.Conn Deadlines are ignored. net.Conn concurrency semantics are supported.
- func (meek *MeekConn) Read(buffer []byte) (n int, err error) {
- if meek.mode != MeekModeRelay {
- return 0, errors.TraceNew("operation unsupported")
- }
- if meek.IsClosed() {
- return 0, errors.TraceNew("meek connection is closed")
- }
- // Block until there is received data to consume
- var receiveBuffer *bytes.Buffer
- select {
- case receiveBuffer = <-meek.partialReceiveBuffer:
- case receiveBuffer = <-meek.fullReceiveBuffer:
- case <-meek.runCtx.Done():
- return 0, errors.TraceNew("meek connection has closed")
- }
- n, err = receiveBuffer.Read(buffer)
- meek.replaceReceiveBuffer(receiveBuffer)
- return n, err
- }
- // 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.mode != MeekModeRelay {
- return 0, errors.TraceNew("operation unsupported")
- }
- if meek.IsClosed() {
- return 0, errors.TraceNew("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.runCtx.Done():
- return 0, errors.TraceNew("meek connection has closed")
- }
- writeLen := meek.limitRequestPayloadLength - 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
- }
- // LocalAddr is a stub implementation of net.Conn.LocalAddr
- func (meek *MeekConn) LocalAddr() net.Addr {
- return nil
- }
- // RemoteAddr is a stub implementation of net.Conn.RemoteAddr
- func (meek *MeekConn) RemoteAddr() net.Addr {
- return nil
- }
- // SetDeadline is a stub implementation of net.Conn.SetDeadline
- func (meek *MeekConn) SetDeadline(t time.Time) error {
- return errors.TraceNew("not supported")
- }
- // SetReadDeadline is a stub implementation of net.Conn.SetReadDeadline
- func (meek *MeekConn) SetReadDeadline(t time.Time) error {
- return errors.TraceNew("not supported")
- }
- // SetWriteDeadline is a stub implementation of net.Conn.SetWriteDeadline
- func (meek *MeekConn) SetWriteDeadline(t time.Time) error {
- return errors.TraceNew("not supported")
- }
- func (meek *MeekConn) replaceReceiveBuffer(receiveBuffer *bytes.Buffer) {
- switch {
- case receiveBuffer.Len() == 0:
- meek.emptyReceiveBuffer <- receiveBuffer
- case receiveBuffer.Len() >= meek.fullReceiveBufferLength:
- meek.fullReceiveBuffer <- receiveBuffer
- default:
- meek.partialReceiveBuffer <- receiveBuffer
- }
- }
- func (meek *MeekConn) replaceSendBuffer(sendBuffer *bytes.Buffer) {
- switch {
- case sendBuffer.Len() == 0:
- meek.emptySendBuffer <- sendBuffer
- case sendBuffer.Len() >= meek.limitRequestPayloadLength:
- meek.fullSendBuffer <- sendBuffer
- default:
- meek.partialSendBuffer <- sendBuffer
- }
- }
- // 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()
- p := meek.getCustomParameters()
- interval := prng.JitterDuration(
- p.Duration(parameters.MeekMinPollInterval),
- p.Float(parameters.MeekMinPollIntervalJitter))
- p.Close()
- timeout := time.NewTimer(interval)
- defer timeout.Stop()
- 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.runCtx.Done():
- // Drop through to second Done() check
- }
- // Check Done() again, to ensure it takes precedence
- select {
- case <-meek.runCtx.Done():
- return
- default:
- }
- sendPayloadSize := 0
- if sendBuffer != nil {
- sendPayloadSize = sendBuffer.Len()
- }
- // relayRoundTrip will replace sendBuffer (by calling replaceSendBuffer). This
- // is a compromise to conserve memory. Using a second buffer here, we could
- // copy sendBuffer and immediately replace it, unblocking meekConn.Write() and
- // allowing more upstream payload to immediately enqueue. Instead, the request
- // payload is read directly from sendBuffer, including retries. Only once the
- // server has acknowledged the request payload is sendBuffer replaced. This
- // still allows meekConn.Write() to unblock before the round trip response is
- // read.
- receivedPayloadSize, err := meek.relayRoundTrip(sendBuffer)
- if err != nil {
- select {
- case <-meek.runCtx.Done():
- // In this case, meek.relayRoundTrip encountered Done(). Exit without
- // logging error.
- return
- default:
- }
- NoticeWarning("%s", errors.Trace(err))
- go meek.Close()
- return
- }
- // Periodically re-dial the underlying TLS/TCP connection
- // (notwithstanding the parameter name, this also applies to TCP
- // connections for HTTP protocols).
- if prng.FlipWeightedCoin(meek.redialTLSProbability) {
- meek.transport.CloseIdleConnections()
- }
- // Calculate polling interval. When data is received,
- // immediately request more. Otherwise, schedule next
- // poll with exponential back off. Jitter and coin
- // flips are used to avoid trivial, static traffic
- // timing patterns.
- p := meek.getCustomParameters()
- if receivedPayloadSize > 0 || sendPayloadSize > 0 {
- interval = 0
- } else if interval == 0 {
- interval = prng.JitterDuration(
- p.Duration(parameters.MeekMinPollInterval),
- p.Float(parameters.MeekMinPollIntervalJitter))
- } else {
- if p.WeightedCoinFlip(parameters.MeekApplyPollIntervalMultiplierProbability) {
- interval =
- time.Duration(float64(interval) *
- p.Float(parameters.MeekPollIntervalMultiplier))
- }
- interval = prng.JitterDuration(
- interval,
- p.Float(parameters.MeekPollIntervalJitter))
- if interval >= p.Duration(parameters.MeekMaxPollInterval) {
- interval = prng.JitterDuration(
- p.Duration(parameters.MeekMaxPollInterval),
- p.Float(parameters.MeekMaxPollIntervalJitter))
- }
- }
- p.Close()
- }
- }
- // readCloseSignaller is an io.ReadCloser wrapper for an io.Reader
- // that is passed, as the request body, to http.Transport.RoundTrip.
- // readCloseSignaller adds the AwaitClosed call, which is used
- // to schedule recycling the buffer underlying the reader only after
- // RoundTrip has called Close and will no longer use the buffer.
- // See: https://golang.org/pkg/net/http/#RoundTripper
- type readCloseSignaller struct {
- context context.Context
- reader io.Reader
- closed chan struct{}
- }
- func NewReadCloseSignaller(
- context context.Context,
- reader io.Reader) *readCloseSignaller {
- return &readCloseSignaller{
- context: context,
- reader: reader,
- closed: make(chan struct{}, 1),
- }
- }
- func (r *readCloseSignaller) Read(p []byte) (int, error) {
- return r.reader.Read(p)
- }
- func (r *readCloseSignaller) Close() error {
- select {
- case r.closed <- struct{}{}:
- default:
- }
- return nil
- }
- func (r *readCloseSignaller) AwaitClosed() bool {
- select {
- case <-r.context.Done():
- case <-r.closed:
- return true
- }
- return false
- }
- // newRequest performs common request setup for both MeekModeRelay and
- // MeekModeObfuscatedRoundTrip.
- //
- // newRequest is not safe for concurrent calls due to its use of
- // setRequestContext.
- //
- // The caller must call the returned cancelFunc.
- func (meek *MeekConn) newRequest(
- requestCtx context.Context,
- cookie *http.Cookie,
- contentType string,
- body io.Reader,
- contentLength int) (*http.Request, error) {
- // Ensure dials are made within the request context.
- meek.setDialerRequestContext(requestCtx)
- request, err := http.NewRequest("POST", meek.url.String(), body)
- if err != nil {
- return nil, errors.Trace(err)
- }
- request = request.WithContext(requestCtx)
- // Content-Length may not be be set automatically due to the
- // underlying type of requestBody.
- if contentLength > 0 {
- request.ContentLength = int64(contentLength)
- }
- meek.addAdditionalHeaders(request)
- request.Header.Set("Content-Type", contentType)
- if cookie == nil {
- cookie = meek.cookie
- }
- request.AddCookie(cookie)
- return request, nil
- }
- // setDialerRequestContext ensures that underlying TLS/QUIC dials operate
- // within the context of the request context. setDialerRequestContext must not
- // be called while another request is already in flight.
- func (meek *MeekConn) setDialerRequestContext(requestCtx context.Context) {
- if meek.isQUIC {
- meek.transport.(*quic.QUICTransporter).SetRequestContext(requestCtx)
- } else if meek.cachedTLSDialer != nil {
- meek.cachedTLSDialer.setRequestContext(requestCtx)
- }
- }
- // Workaround for h2quic.RoundTripper context issue. See comment in
- // MeekConn.Close.
- func (meek *MeekConn) scheduleQUICCloseIdle(request *http.Request) {
- requestCtx := request.Context()
- if meek.isQUIC && requestCtx != context.Background() {
- go func() {
- <-requestCtx.Done()
- meek.transport.CloseIdleConnections()
- }()
- }
- }
- // relayRoundTrip configures and makes the actual HTTP POST request
- func (meek *MeekConn) relayRoundTrip(sendBuffer *bytes.Buffer) (int64, error) {
- // Retries are made when the round trip fails. This adds resiliency
- // to connection interruption and intermittent failures.
- //
- // At least one retry is always attempted, and retries continue
- // while still within a brief deadline -- 5 seconds, currently the
- // deadline for an actively probed SSH connection to timeout. There
- // is a brief delay between retries, allowing for intermittent
- // failure states to resolve.
- //
- // Failure may occur at various stages of the HTTP request:
- //
- // 1. Before the request begins. In this case, the entire request
- // may be rerun.
- //
- // 2. While sending the request payload. In this case, the client
- // must resend its request payload. The server will not have
- // relayed its partially received request payload.
- //
- // 3. After sending the request payload but before receiving
- // a response. The client cannot distinguish between case 2 and
- // this case, case 3. The client resends its payload and the
- // server detects this and skips relaying the request payload.
- //
- // 4. While reading the response payload. The client will omit its
- // request payload when retrying, as the server has already
- // acknowledged it. The client will also indicate to the server
- // the amount of response payload already received, and the
- // server will skip resending the indicated amount of response
- // payload.
- //
- // Retries are indicated to the server by adding a Range header,
- // which includes the response payload resend position.
- defer func() {
- // Ensure sendBuffer is replaced, even in error code paths.
- if sendBuffer != nil {
- sendBuffer.Truncate(0)
- meek.replaceSendBuffer(sendBuffer)
- }
- }()
- retries := uint(0)
- p := meek.getCustomParameters()
- retryDeadline := time.Now().Add(p.Duration(parameters.MeekRoundTripRetryDeadline))
- retryDelay := p.Duration(parameters.MeekRoundTripRetryMinDelay)
- retryMaxDelay := p.Duration(parameters.MeekRoundTripRetryMaxDelay)
- retryMultiplier := p.Float(parameters.MeekRoundTripRetryMultiplier)
- p.Close()
- serverAcknowledgedRequestPayload := false
- receivedPayloadSize := int64(0)
- for try := 0; ; try++ {
- // Omit the request payload when retrying after receiving a
- // partial server response.
- var signaller *readCloseSignaller
- var requestBody io.ReadCloser
- contentLength := 0
- if !serverAcknowledgedRequestPayload && sendBuffer != nil {
- // sendBuffer will be replaced once the data is no longer needed,
- // when RoundTrip calls Close on the Body; this allows meekConn.Write()
- // to unblock and start buffering data for the next roung trip while
- // still reading the current round trip response. signaller provides
- // the hook for awaiting RoundTrip's call to Close.
- signaller = NewReadCloseSignaller(meek.runCtx, bytes.NewReader(sendBuffer.Bytes()))
- requestBody = signaller
- contentLength = sendBuffer.Len()
- }
- // - meek.stopRunning() will abort a round trip in flight
- // - round trip will abort if it exceeds timeout
- requestCtx, cancelFunc := context.WithTimeout(
- meek.runCtx,
- meek.getCustomParameters().Duration(parameters.MeekRoundTripTimeout))
- defer cancelFunc()
- request, err := meek.newRequest(
- requestCtx,
- nil,
- meek.contentType,
- requestBody,
- contentLength)
- if err != nil {
- // Don't retry when can't initialize a Request
- return 0, errors.Trace(err)
- }
- expectedStatusCode := http.StatusOK
- // When retrying, add a Range header to indicate how much
- // of the response was already received.
- if try > 0 {
- expectedStatusCode = http.StatusPartialContent
- request.Header.Set("Range", fmt.Sprintf("bytes=%d-", receivedPayloadSize))
- }
- response, err := meek.transport.RoundTrip(request)
- // Wait for RoundTrip to call Close on the request body, when
- // there is one. This is necessary to ensure it's safe to
- // subsequently replace sendBuffer in both the success and
- // error cases.
- if signaller != nil {
- if !signaller.AwaitClosed() {
- // AwaitClosed encountered Done(). Abort immediately. Do not
- // replace sendBuffer, as we cannot be certain RoundTrip is
- // done with it. MeekConn.Write will exit on Done and not hang
- // awaiting sendBuffer.
- sendBuffer = nil
- return 0, errors.TraceNew("meek connection has closed")
- }
- }
- if err != nil {
- select {
- case <-meek.runCtx.Done():
- // Exit without retrying and without logging error.
- return 0, errors.Trace(err)
- default:
- }
- NoticeWarning("meek round trip failed: %s", err)
- // ...continue to retry
- }
- if err == nil {
- if response.StatusCode != expectedStatusCode &&
- // Certain http servers return 200 OK where we expect 206, so accept that.
- !(expectedStatusCode == http.StatusPartialContent && response.StatusCode == http.StatusOK) {
- // Don't retry when the status code is incorrect
- response.Body.Close()
- return 0, errors.Tracef(
- "unexpected status code: %d instead of %d",
- response.StatusCode, expectedStatusCode)
- }
- // Update meek session cookie
- for _, c := range response.Cookies() {
- if meek.cookie.Name == c.Name {
- meek.cookie.Value = c.Value
- break
- }
- }
- // Received the response status code, so the server
- // must have received the request payload.
- serverAcknowledgedRequestPayload = true
- // sendBuffer is now no longer required for retries, and the
- // buffer may be replaced; this allows meekConn.Write() to unblock
- // and start buffering data for the next round trip while still
- // reading the current round trip response.
- if sendBuffer != nil {
- // Assumes signaller.AwaitClosed is called above, so
- // sendBuffer will no longer be accessed by RoundTrip.
- sendBuffer.Truncate(0)
- meek.replaceSendBuffer(sendBuffer)
- sendBuffer = nil
- }
- readPayloadSize, err := meek.readPayload(response.Body)
- response.Body.Close()
- // receivedPayloadSize is the number of response
- // payload bytes received and relayed. A retry can
- // resume after this position.
- receivedPayloadSize += readPayloadSize
- if err != nil {
- NoticeWarning("meek read payload failed: %s", err)
- // ...continue to retry
- } else {
- // Round trip completed successfully
- break
- }
- }
- // Release context resources immediately.
- cancelFunc()
- // Either the request failed entirely, or there was a failure
- // streaming the response payload. Always retry once. Then
- // retry if time remains; when the next delay exceeds the time
- // remaining until the deadline, do not retry.
- now := time.Now()
- if retries >= 1 &&
- (now.After(retryDeadline) || retryDeadline.Sub(now) <= retryDelay) {
- return 0, errors.Trace(err)
- }
- retries += 1
- delayTimer := time.NewTimer(retryDelay)
- select {
- case <-delayTimer.C:
- case <-meek.runCtx.Done():
- delayTimer.Stop()
- return 0, errors.Trace(err)
- }
- // Increase the next delay, to back off and avoid excessive
- // activity in conditions such as no network connectivity.
- retryDelay = time.Duration(
- float64(retryDelay) * retryMultiplier)
- if retryDelay >= retryMaxDelay {
- retryDelay = retryMaxDelay
- }
- }
- return receivedPayloadSize, nil
- }
- // Add additional headers to the HTTP request using the same method we use for adding
- // custom headers to HTTP proxy requests.
- func (meek *MeekConn) addAdditionalHeaders(request *http.Request) {
- for name, value := range meek.additionalHeaders {
- if name == "Host" {
- if len(value) > 0 {
- request.Host = value[0]
- }
- } else {
- request.Header[name] = value
- }
- }
- }
- // readPayload reads the HTTP response in chunks, making the read buffer available
- // to MeekConn.Read() calls after each chunk; the intention is to allow bytes to
- // flow back to the reader as soon as possible instead of buffering the entire payload.
- //
- // When readPayload returns an error, the totalSize output is remains valid -- it's the
- // number of payload bytes successfully read and relayed.
- func (meek *MeekConn) readPayload(
- receivedPayload io.ReadCloser) (totalSize int64, err error) {
- defer receivedPayload.Close()
- totalSize = 0
- for {
- reader := io.LimitReader(receivedPayload, int64(meek.readPayloadChunkLength))
- // Block until there is capacity in the receive buffer
- var receiveBuffer *bytes.Buffer
- select {
- case receiveBuffer = <-meek.emptyReceiveBuffer:
- case receiveBuffer = <-meek.partialReceiveBuffer:
- case <-meek.runCtx.Done():
- return 0, nil
- }
- // Note: receiveBuffer size may exceed meek.fullReceiveBufferLength by up to the size
- // of one received payload. The meek.fullReceiveBufferLength value is just a guideline.
- n, err := receiveBuffer.ReadFrom(reader)
- meek.replaceReceiveBuffer(receiveBuffer)
- totalSize += n
- if err != nil {
- return totalSize, errors.Trace(err)
- }
- if n == 0 {
- break
- }
- }
- return totalSize, nil
- }
- // makeMeekObfuscationValues creates the meek cookie, to be sent with initial
- // meek HTTP request, and other meek obfuscation values. The cookies contains
- // obfuscated metadata, including meek version and other protocol information.
- //
- // In round tripper mode, the cookie contains the destination endpoint for the
- // round trip request.
- //
- // In relay mode, the server will create a session using the cookie values and
- // send the session ID back to the client via Set-Cookie header. The client
- // must use that value with all consequent HTTP requests.
- //
- // In plain HTTP meek protocols, the cookie is visible over the adversary
- // network, so the cookie is encrypted and obfuscated.
- //
- // Obsolete meek cookie fields used by the legacy server stack are no longer
- // sent. These include ServerAddress and SessionID.
- //
- // The request payload limit and TLS redial probability apply only to relay
- // mode and are selected once and used for the duration of a meek connction.
- func makeMeekObfuscationValues(
- p parameters.ParametersAccessor,
- meekCookieEncryptionPublicKey string,
- meekObfuscatedKey string,
- meekObfuscatorPaddingPRNGSeed *prng.Seed,
- clientTunnelProtocol string,
- endPoint string,
- ) (cookie *http.Cookie,
- contentType string,
- tlsPadding int,
- limitRequestPayloadLength int,
- redialTLSProbability float64,
- err error) {
- if meekCookieEncryptionPublicKey == "" {
- return nil, "", 0, 0, 0.0, errors.TraceNew("missing public key")
- }
- cookieData := &protocol.MeekCookieData{
- MeekProtocolVersion: MEEK_PROTOCOL_VERSION,
- ClientTunnelProtocol: clientTunnelProtocol,
- EndPoint: endPoint,
- }
- serializedCookie, err := json.Marshal(cookieData)
- if err != nil {
- return nil, "", 0, 0, 0.0, errors.Trace(err)
- }
- // Encrypt the JSON data
- // NaCl box is used for encryption. The peer public key comes from the server entry.
- // Nonce is always all zeros, and is not sent in the cookie (the server also uses an all-zero nonce).
- // http://nacl.cace-project.eu/box.html:
- // "There is no harm in having the same nonce for different messages if the {sender, receiver} sets are
- // different. This is true even if the sets overlap. For example, a sender can use the same nonce for two
- // different messages if the messages are sent to two different public keys."
- var nonce [24]byte
- var publicKey [32]byte
- decodedPublicKey, err := base64.StdEncoding.DecodeString(meekCookieEncryptionPublicKey)
- if err != nil {
- return nil, "", 0, 0, 0.0, errors.Trace(err)
- }
- copy(publicKey[:], decodedPublicKey)
- ephemeralPublicKey, ephemeralPrivateKey, err := box.GenerateKey(rand.Reader)
- if err != nil {
- return nil, "", 0, 0, 0.0, errors.Trace(err)
- }
- box := box.Seal(nil, serializedCookie, &nonce, &publicKey, ephemeralPrivateKey)
- encryptedCookie := make([]byte, 32+len(box))
- copy(encryptedCookie[0:32], ephemeralPublicKey[0:32])
- copy(encryptedCookie[32:], box)
- maxPadding := p.Int(parameters.MeekCookieMaxPadding)
- // Obfuscate the encrypted data. NewClientObfuscator checks that
- // meekObfuscatedKey isn't missing.
- obfuscator, err := obfuscator.NewClientObfuscator(
- &obfuscator.ObfuscatorConfig{
- Keyword: meekObfuscatedKey,
- PaddingPRNGSeed: meekObfuscatorPaddingPRNGSeed,
- MaxPadding: &maxPadding})
- if err != nil {
- return nil, "", 0, 0, 0.0, errors.Trace(err)
- }
- obfuscatedCookie, _ := obfuscator.SendPreamble()
- seedLen := len(obfuscatedCookie)
- obfuscatedCookie = append(obfuscatedCookie, encryptedCookie...)
- obfuscator.ObfuscateClientToServer(obfuscatedCookie[seedLen:])
- cookieNamePRNG, err := obfuscator.GetDerivedPRNG("meek-cookie-name")
- if err != nil {
- return nil, "", 0, 0, 0.0, errors.Trace(err)
- }
- var cookieName string
- if cookieNamePRNG.FlipWeightedCoin(p.Float(parameters.MeekAlternateCookieNameProbability)) {
- cookieName = values.GetCookieName(cookieNamePRNG)
- } else {
- // Format the HTTP cookie
- // The format is <random letter 'A'-'Z'>=<base64 data>, which is intended to match common cookie formats.
- A := int('A')
- Z := int('Z')
- // letterIndex is integer in range [int('A'), int('Z')]
- letterIndex := cookieNamePRNG.Intn(Z - A + 1)
- cookieName = string(byte(A + letterIndex))
- }
- cookie = &http.Cookie{
- Name: cookieName,
- Value: base64.StdEncoding.EncodeToString(obfuscatedCookie)}
- contentTypePRNG, err := obfuscator.GetDerivedPRNG("meek-content-type")
- if err != nil {
- return nil, "", 0, 0, 0.0, errors.Trace(err)
- }
- if contentTypePRNG.FlipWeightedCoin(p.Float(parameters.MeekAlternateContentTypeProbability)) {
- contentType = values.GetContentType(contentTypePRNG)
- } else {
- contentType = "application/octet-stream"
- }
- tlsPadding = 0
- limitRequestPayloadLength = MEEK_MAX_REQUEST_PAYLOAD_LENGTH
- redialTLSProbability = 0.0
- tunnelProtocols := p.TunnelProtocols(parameters.MeekTrafficShapingLimitProtocols)
- if (len(tunnelProtocols) == 0 ||
- common.Contains(tunnelProtocols, clientTunnelProtocol)) &&
- p.WeightedCoinFlip(parameters.MeekTrafficShapingProbability) {
- limitRequestPayloadLengthPRNG, err := obfuscator.GetDerivedPRNG(
- "meek-limit-request-payload-length")
- if err != nil {
- return nil, "", 0, 0, 0.0, errors.Trace(err)
- }
- minLength := p.Int(parameters.MeekMinLimitRequestPayloadLength)
- if minLength > MEEK_MAX_REQUEST_PAYLOAD_LENGTH {
- minLength = MEEK_MAX_REQUEST_PAYLOAD_LENGTH
- }
- maxLength := p.Int(parameters.MeekMaxLimitRequestPayloadLength)
- if maxLength > MEEK_MAX_REQUEST_PAYLOAD_LENGTH {
- maxLength = MEEK_MAX_REQUEST_PAYLOAD_LENGTH
- }
- limitRequestPayloadLength = limitRequestPayloadLengthPRNG.Range(
- minLength, maxLength)
- minPadding := p.Int(parameters.MeekMinTLSPadding)
- maxPadding := p.Int(parameters.MeekMaxTLSPadding)
- // Maximum padding size per RFC 7685
- if maxPadding > 65535 {
- maxPadding = 65535
- }
- if maxPadding > 0 {
- tlsPaddingPRNG, err := obfuscator.GetDerivedPRNG(
- "meek-tls-padding")
- if err != nil {
- return nil, "", 0, 0, 0.0, errors.Trace(err)
- }
- tlsPadding = tlsPaddingPRNG.Range(minPadding, maxPadding)
- }
- redialTLSProbability = p.Float(parameters.MeekRedialTLSProbability)
- }
- return cookie, contentType, tlsPadding, limitRequestPayloadLength, redialTLSProbability, nil
- }
|