github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/net.go (about)

     1  /*
     2   * Copyright (c) 2015, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package psiphon
    21  
    22  import (
    23  	"context"
    24  	"crypto/tls"
    25  	"crypto/x509"
    26  	std_errors "errors"
    27  	"fmt"
    28  	"io"
    29  	"io/ioutil"
    30  	"net"
    31  	"net/http"
    32  	"os"
    33  	"strings"
    34  	"sync"
    35  	"sync/atomic"
    36  	"time"
    37  
    38  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
    39  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
    40  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/fragmentor"
    41  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
    42  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/resolver"
    43  	"golang.org/x/net/bpf"
    44  )
    45  
    46  // DialConfig contains parameters to determine the behavior
    47  // of a Psiphon dialer (TCPDial, UDPDial, MeekDial, etc.)
    48  type DialConfig struct {
    49  
    50  	// DiagnosticID is the server ID to record in any diagnostics notices.
    51  	DiagnosticID string
    52  
    53  	// UpstreamProxyURL specifies a proxy to connect through.
    54  	// E.g., "http://proxyhost:8080"
    55  	//       "socks5://user:password@proxyhost:1080"
    56  	//       "socks4a://proxyhost:1080"
    57  	//       "http://NTDOMAIN\NTUser:password@proxyhost:3375"
    58  	//
    59  	// Certain tunnel protocols require HTTP CONNECT support
    60  	// when a HTTP proxy is specified. If CONNECT is not
    61  	// supported, those protocols will not connect.
    62  	//
    63  	// UpstreamProxyURL is not used by UDPDial.
    64  	UpstreamProxyURL string
    65  
    66  	// CustomHeaders is a set of additional arbitrary HTTP headers that are
    67  	// added to all plaintext HTTP requests and requests made through an HTTP
    68  	// upstream proxy when specified by UpstreamProxyURL.
    69  	CustomHeaders http.Header
    70  
    71  	// BPFProgramInstructions specifies a BPF program to attach to the dial
    72  	// socket before connecting.
    73  	BPFProgramInstructions []bpf.RawInstruction
    74  
    75  	// DeviceBinder, when not nil, is applied when dialing UDP/TCP. See:
    76  	// DeviceBinder doc.
    77  	DeviceBinder DeviceBinder
    78  
    79  	// IPv6Synthesizer, when not nil, is applied when dialing UDP/TCP. See:
    80  	// IPv6Synthesizer doc.
    81  	IPv6Synthesizer IPv6Synthesizer
    82  
    83  	// ResolveIP is used to resolve destination domains. ResolveIP should
    84  	// return either at least one IP address or an error.
    85  	ResolveIP func(context.Context, string) ([]net.IP, error)
    86  
    87  	// ResolvedIPCallback, when set, is called with the IP address that was
    88  	// dialed. This is either the specified IP address in the dial address,
    89  	// or the resolved IP address in the case where the dial address is a
    90  	// domain name.
    91  	// The callback may be invoked by a concurrent goroutine.
    92  	ResolvedIPCallback func(string)
    93  
    94  	// TrustedCACertificatesFilename specifies a file containing trusted
    95  	// CA certs. See Config.TrustedCACertificatesFilename.
    96  	TrustedCACertificatesFilename string
    97  
    98  	// FragmentorConfig specifies whether to layer a fragmentor.Conn on top
    99  	// of dialed TCP conns, and the fragmentation configuration to use.
   100  	FragmentorConfig *fragmentor.Config
   101  
   102  	// UpstreamProxyErrorCallback is called when a dial fails due to an upstream
   103  	// proxy error. As the upstream proxy is user configured, the error message
   104  	// may need to be relayed to the user.
   105  	UpstreamProxyErrorCallback func(error)
   106  
   107  	// CustomDialer overrides the dialer created by NewNetDialer/NewTCPDialer.
   108  	// When CustomDialer is set, all other DialConfig parameters are ignored by
   109  	// NewNetDialer/NewTCPDialer. Other DialConfig consumers may still reference
   110  	// other DialConfig parameters; for example MeekConfig still uses
   111  	// TrustedCACertificatesFilename.
   112  	CustomDialer common.Dialer
   113  }
   114  
   115  // WithoutFragmentor returns a copy of the DialConfig with any fragmentor
   116  // configuration disabled. The return value is not a deep copy and may be the
   117  // input DialConfig; it should not be modified.
   118  func (config *DialConfig) WithoutFragmentor() *DialConfig {
   119  	if config.FragmentorConfig == nil {
   120  		return config
   121  	}
   122  	newConfig := new(DialConfig)
   123  	*newConfig = *config
   124  	newConfig.FragmentorConfig = nil
   125  	return newConfig
   126  }
   127  
   128  // NetworkConnectivityChecker defines the interface to the external
   129  // HasNetworkConnectivity provider, which call into the host application to
   130  // check for network connectivity.
   131  type NetworkConnectivityChecker interface {
   132  	// TODO: change to bool return value once gobind supports that type
   133  	HasNetworkConnectivity() int
   134  }
   135  
   136  // DeviceBinder defines the interface to the external BindToDevice provider
   137  // which calls into the host application to bind sockets to specific devices.
   138  // This is used for VPN routing exclusion.
   139  // The string return value should report device information for diagnostics.
   140  type DeviceBinder interface {
   141  	BindToDevice(fileDescriptor int) (string, error)
   142  }
   143  
   144  // DNSServerGetter defines the interface to the external GetDNSServers provider
   145  // which calls into the host application to discover the native network DNS
   146  // server settings.
   147  type DNSServerGetter interface {
   148  	GetDNSServers() []string
   149  }
   150  
   151  // IPv6Synthesizer defines the interface to the external IPv6Synthesize
   152  // provider which calls into the host application to synthesize IPv6 addresses
   153  // from IPv4 ones. This is used to correctly lookup IPs on DNS64/NAT64
   154  // networks.
   155  type IPv6Synthesizer interface {
   156  	IPv6Synthesize(IPv4Addr string) string
   157  }
   158  
   159  // HasIPv6RouteGetter defines the interface to the external HasIPv6Route
   160  // provider which calls into the host application to determine if the host
   161  // has an IPv6 route.
   162  type HasIPv6RouteGetter interface {
   163  	// TODO: change to bool return value once gobind supports that type
   164  	HasIPv6Route() int
   165  }
   166  
   167  // NetworkIDGetter defines the interface to the external GetNetworkID
   168  // provider, which returns an identifier for the host's current active
   169  // network.
   170  //
   171  // The identifier is a string that should indicate the network type and
   172  // identity; for example "WIFI-<BSSID>" or "MOBILE-<MCC/MNC>". As this network
   173  // ID is personally identifying, it is only used locally in the client to
   174  // determine network context and is not sent to the Psiphon server. The
   175  // identifer will be logged in diagnostics messages; in this case only the
   176  // substring before the first "-" is logged, so all PII must appear after the
   177  // first "-".
   178  //
   179  // NetworkIDGetter.GetNetworkID should always return an identifier value, as
   180  // logic that uses GetNetworkID, including tactics, is intended to proceed
   181  // regardless of whether an accurate network identifier can be obtained. By
   182  // convention, the provider should return "UNKNOWN" when an accurate network
   183  // identifier cannot be obtained. Best-effort is acceptable: e.g., return just
   184  // "WIFI" when only the type of the network but no details can be determined.
   185  type NetworkIDGetter interface {
   186  	GetNetworkID() string
   187  }
   188  
   189  // NetDialer implements an interface that matches net.Dialer.
   190  // Limitation: only "tcp" Dials are supported.
   191  type NetDialer struct {
   192  	dialTCP common.Dialer
   193  }
   194  
   195  // NewNetDialer creates a new NetDialer.
   196  func NewNetDialer(config *DialConfig) *NetDialer {
   197  	return &NetDialer{
   198  		dialTCP: NewTCPDialer(config),
   199  	}
   200  }
   201  
   202  func (d *NetDialer) Dial(network, address string) (net.Conn, error) {
   203  	conn, err := d.DialContext(context.Background(), network, address)
   204  	if err != nil {
   205  		return nil, errors.Trace(err)
   206  	}
   207  	return conn, nil
   208  }
   209  
   210  func (d *NetDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
   211  	switch network {
   212  	case "tcp":
   213  		conn, err := d.dialTCP(ctx, "tcp", address)
   214  		if err != nil {
   215  			return nil, errors.Trace(err)
   216  		}
   217  		return conn, nil
   218  	default:
   219  		return nil, errors.Tracef("unsupported network: %s", network)
   220  	}
   221  }
   222  
   223  // LocalProxyRelay sends to remoteConn bytes received from localConn,
   224  // and sends to localConn bytes received from remoteConn.
   225  //
   226  // LocalProxyRelay must close localConn in order to interrupt blocking
   227  // I/O calls when the upstream port forward is closed. remoteConn is
   228  // also closed before returning.
   229  func LocalProxyRelay(config *Config, proxyType string, localConn, remoteConn net.Conn) {
   230  
   231  	closing := int32(0)
   232  
   233  	copyWaitGroup := new(sync.WaitGroup)
   234  	copyWaitGroup.Add(1)
   235  
   236  	go func() {
   237  		defer copyWaitGroup.Done()
   238  
   239  		_, err := RelayCopyBuffer(config, localConn, remoteConn)
   240  		if err != nil && atomic.LoadInt32(&closing) != 1 {
   241  			NoticeLocalProxyError(proxyType, errors.TraceMsg(err, "Relay failed"))
   242  		}
   243  
   244  		// When the server closes a port forward, ex. due to idle timeout,
   245  		// remoteConn.Read will return EOF, which causes the downstream io.Copy to
   246  		// return (with a nil error). To ensure the downstream local proxy
   247  		// connection also closes at this point, we interrupt the blocking upstream
   248  		// io.Copy by closing localConn.
   249  
   250  		atomic.StoreInt32(&closing, 1)
   251  		localConn.Close()
   252  	}()
   253  
   254  	_, err := RelayCopyBuffer(config, remoteConn, localConn)
   255  	if err != nil && atomic.LoadInt32(&closing) != 1 {
   256  		NoticeLocalProxyError(proxyType, errors.TraceMsg(err, "Relay failed"))
   257  	}
   258  
   259  	// When a local proxy peer connection closes, localConn.Read will return EOF.
   260  	// As above, close the other end of the relay to ensure immediate shutdown,
   261  	// as no more data can be relayed.
   262  
   263  	atomic.StoreInt32(&closing, 1)
   264  	remoteConn.Close()
   265  
   266  	copyWaitGroup.Wait()
   267  }
   268  
   269  // RelayCopyBuffer performs an io.Copy, optionally using a smaller buffer when
   270  // config.LimitRelayBufferSizes is set.
   271  func RelayCopyBuffer(config *Config, dst io.Writer, src io.Reader) (int64, error) {
   272  
   273  	// By default, io.CopyBuffer will allocate a 32K buffer when a nil buffer
   274  	// is passed in. When configured, make and specify a smaller buffer. But
   275  	// only if src doesn't implement WriterTo and dst doesn't implement
   276  	// ReaderFrom, as in those cases io.CopyBuffer entirely avoids a buffer
   277  	// allocation.
   278  
   279  	var buffer []byte
   280  	if config.LimitRelayBufferSizes {
   281  		_, isWT := src.(io.WriterTo)
   282  		_, isRF := dst.(io.ReaderFrom)
   283  		if !isWT && !isRF {
   284  			buffer = make([]byte, 4096)
   285  		}
   286  	}
   287  
   288  	// Do not wrap any I/O errors
   289  	return io.CopyBuffer(dst, src, buffer)
   290  }
   291  
   292  // WaitForNetworkConnectivity uses a NetworkConnectivityChecker to
   293  // periodically check for network connectivity. It returns true if
   294  // no NetworkConnectivityChecker is provided (waiting is disabled)
   295  // or when NetworkConnectivityChecker.HasNetworkConnectivity()
   296  // indicates connectivity. It waits and polls the checker once a second.
   297  // When the context is done, false is returned immediately.
   298  func WaitForNetworkConnectivity(
   299  	ctx context.Context, connectivityChecker NetworkConnectivityChecker) bool {
   300  
   301  	if connectivityChecker == nil || connectivityChecker.HasNetworkConnectivity() == 1 {
   302  		return true
   303  	}
   304  
   305  	NoticeInfo("waiting for network connectivity")
   306  
   307  	ticker := time.NewTicker(1 * time.Second)
   308  	defer ticker.Stop()
   309  
   310  	for {
   311  		if connectivityChecker.HasNetworkConnectivity() == 1 {
   312  			return true
   313  		}
   314  
   315  		select {
   316  		case <-ticker.C:
   317  			// Check HasNetworkConnectivity again
   318  		case <-ctx.Done():
   319  			return false
   320  		}
   321  	}
   322  }
   323  
   324  // New Resolver creates a new resolver using the specified config.
   325  // useBindToDevice indicates whether to apply config.BindToDevice, when it
   326  // exists; set useBindToDevice to false when the resolve doesn't need to be
   327  // excluded from any VPN routing.
   328  func NewResolver(config *Config, useBindToDevice bool) *resolver.Resolver {
   329  
   330  	p := config.GetParameters().Get()
   331  
   332  	networkConfig := &resolver.NetworkConfig{
   333  		LogWarning:                func(err error) { NoticeWarning("ResolveIP: %v", err) },
   334  		LogHostnames:              config.EmitDiagnosticNetworkParameters,
   335  		CacheExtensionInitialTTL:  p.Duration(parameters.DNSResolverCacheExtensionInitialTTL),
   336  		CacheExtensionVerifiedTTL: p.Duration(parameters.DNSResolverCacheExtensionVerifiedTTL),
   337  	}
   338  
   339  	if config.DNSServerGetter != nil {
   340  		networkConfig.GetDNSServers = config.DNSServerGetter.GetDNSServers
   341  	}
   342  
   343  	if useBindToDevice && config.DeviceBinder != nil {
   344  		networkConfig.BindToDevice = config.DeviceBinder.BindToDevice
   345  		networkConfig.AllowDefaultResolverWithBindToDevice =
   346  			config.AllowDefaultDNSResolverWithBindToDevice
   347  	}
   348  
   349  	if config.IPv6Synthesizer != nil {
   350  		networkConfig.IPv6Synthesize = config.IPv6Synthesizer.IPv6Synthesize
   351  	}
   352  
   353  	if config.HasIPv6RouteGetter != nil {
   354  		networkConfig.HasIPv6Route = func() bool {
   355  			return config.HasIPv6RouteGetter.HasIPv6Route() == 1
   356  		}
   357  	}
   358  
   359  	return resolver.NewResolver(networkConfig, config.GetNetworkID())
   360  }
   361  
   362  // UntunneledResolveIP is used to resolve domains for untunneled dials,
   363  // including remote server list and upgrade downloads.
   364  func UntunneledResolveIP(
   365  	ctx context.Context,
   366  	config *Config,
   367  	resolver *resolver.Resolver,
   368  	hostname string) ([]net.IP, error) {
   369  
   370  	// Limitations: for untunneled resolves, there is currently no resolve
   371  	// parameter replay, and no support for pre-resolved IPs.
   372  
   373  	params, err := resolver.MakeResolveParameters(
   374  		config.GetParameters().Get(), "")
   375  	if err != nil {
   376  		return nil, errors.Trace(err)
   377  	}
   378  
   379  	IPs, err := resolver.ResolveIP(
   380  		ctx,
   381  		config.GetNetworkID(),
   382  		params,
   383  		hostname)
   384  	if err != nil {
   385  		return nil, errors.Trace(err)
   386  	}
   387  
   388  	return IPs, nil
   389  }
   390  
   391  // MakeUntunneledHTTPClient returns a net/http.Client which is configured to
   392  // use custom dialing features -- including BindToDevice, etc.
   393  //
   394  // The context is applied to underlying TCP dials. The caller is responsible
   395  // for applying the context to requests made with the returned http.Client.
   396  func MakeUntunneledHTTPClient(
   397  	ctx context.Context,
   398  	config *Config,
   399  	untunneledDialConfig *DialConfig,
   400  	skipVerify bool) (*http.Client, error) {
   401  
   402  	dialer := NewTCPDialer(untunneledDialConfig)
   403  
   404  	tlsConfig := &CustomTLSConfig{
   405  		Parameters:                    config.GetParameters(),
   406  		Dial:                          dialer,
   407  		UseDialAddrSNI:                true,
   408  		SNIServerName:                 "",
   409  		SkipVerify:                    skipVerify,
   410  		TrustedCACertificatesFilename: untunneledDialConfig.TrustedCACertificatesFilename,
   411  	}
   412  	tlsConfig.EnableClientSessionCache()
   413  
   414  	tlsDialer := NewCustomTLSDialer(tlsConfig)
   415  
   416  	transport := &http.Transport{
   417  		Dial: func(network, addr string) (net.Conn, error) {
   418  			return dialer(ctx, network, addr)
   419  		},
   420  		DialTLS: func(network, addr string) (net.Conn, error) {
   421  			return tlsDialer(ctx, network, addr)
   422  		},
   423  	}
   424  
   425  	httpClient := &http.Client{
   426  		Transport: transport,
   427  	}
   428  
   429  	return httpClient, nil
   430  }
   431  
   432  // MakeTunneledHTTPClient returns a net/http.Client which is
   433  // configured to use custom dialing features including tunneled
   434  // dialing and, optionally, UseTrustedCACertificatesForStockTLS.
   435  // This http.Client uses stock TLS for HTTPS.
   436  func MakeTunneledHTTPClient(
   437  	config *Config,
   438  	tunnel *Tunnel,
   439  	skipVerify bool) (*http.Client, error) {
   440  
   441  	// Note: there is no dial context since SSH port forward dials cannot
   442  	// be interrupted directly. Closing the tunnel will interrupt the dials.
   443  
   444  	tunneledDialer := func(_, addr string) (net.Conn, error) {
   445  		// Set alwaysTunneled to ensure the http.Client traffic is always tunneled,
   446  		// even when split tunnel mode is enabled.
   447  		conn, _, err := tunnel.DialTCPChannel(addr, true, nil)
   448  		return conn, errors.Trace(err)
   449  	}
   450  
   451  	transport := &http.Transport{
   452  		Dial: tunneledDialer,
   453  	}
   454  
   455  	if skipVerify {
   456  
   457  		transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
   458  
   459  	} else if config.TrustedCACertificatesFilename != "" {
   460  
   461  		rootCAs := x509.NewCertPool()
   462  		certData, err := ioutil.ReadFile(config.TrustedCACertificatesFilename)
   463  		if err != nil {
   464  			return nil, errors.Trace(err)
   465  		}
   466  		rootCAs.AppendCertsFromPEM(certData)
   467  		transport.TLSClientConfig = &tls.Config{RootCAs: rootCAs}
   468  	}
   469  
   470  	return &http.Client{
   471  		Transport: transport,
   472  	}, nil
   473  }
   474  
   475  // MakeDownloadHTTPClient is a helper that sets up a http.Client
   476  // for use either untunneled or through a tunnel.
   477  func MakeDownloadHTTPClient(
   478  	ctx context.Context,
   479  	config *Config,
   480  	tunnel *Tunnel,
   481  	untunneledDialConfig *DialConfig,
   482  	skipVerify bool) (*http.Client, bool, error) {
   483  
   484  	var httpClient *http.Client
   485  	var err error
   486  
   487  	tunneled := tunnel != nil
   488  
   489  	if tunneled {
   490  
   491  		httpClient, err = MakeTunneledHTTPClient(
   492  			config, tunnel, skipVerify)
   493  		if err != nil {
   494  			return nil, false, errors.Trace(err)
   495  		}
   496  
   497  	} else {
   498  
   499  		httpClient, err = MakeUntunneledHTTPClient(
   500  			ctx, config, untunneledDialConfig, skipVerify)
   501  		if err != nil {
   502  			return nil, false, errors.Trace(err)
   503  		}
   504  	}
   505  
   506  	return httpClient, tunneled, nil
   507  }
   508  
   509  // ResumeDownload is a reusable helper that downloads requestUrl via the
   510  // httpClient, storing the result in downloadFilename when the download is
   511  // complete. Intermediate, partial downloads state is stored in
   512  // downloadFilename.part and downloadFilename.part.etag.
   513  // Any existing downloadFilename file will be overwritten.
   514  //
   515  // In the case where the remote object has changed while a partial download
   516  // is to be resumed, the partial state is reset and resumeDownload fails.
   517  // The caller must restart the download.
   518  //
   519  // When ifNoneMatchETag is specified, no download is made if the remote
   520  // object has the same ETag. ifNoneMatchETag has an effect only when no
   521  // partial download is in progress.
   522  //
   523  func ResumeDownload(
   524  	ctx context.Context,
   525  	httpClient *http.Client,
   526  	downloadURL string,
   527  	userAgent string,
   528  	downloadFilename string,
   529  	ifNoneMatchETag string) (int64, string, error) {
   530  
   531  	partialFilename := fmt.Sprintf("%s.part", downloadFilename)
   532  
   533  	partialETagFilename := fmt.Sprintf("%s.part.etag", downloadFilename)
   534  
   535  	file, err := os.OpenFile(partialFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
   536  	if err != nil {
   537  		return 0, "", errors.Trace(err)
   538  	}
   539  	defer file.Close()
   540  
   541  	fileInfo, err := file.Stat()
   542  	if err != nil {
   543  		return 0, "", errors.Trace(err)
   544  	}
   545  
   546  	// A partial download should have an ETag which is to be sent with the
   547  	// Range request to ensure that the source object is the same as the
   548  	// one that is partially downloaded.
   549  	var partialETag []byte
   550  	if fileInfo.Size() > 0 {
   551  
   552  		partialETag, err = ioutil.ReadFile(partialETagFilename)
   553  
   554  		// When the ETag can't be loaded, delete the partial download. To keep the
   555  		// code simple, there is no immediate, inline retry here, on the assumption
   556  		// that the controller's upgradeDownloader will shortly call DownloadUpgrade
   557  		// again.
   558  		if err != nil {
   559  
   560  			// On Windows, file must be closed before it can be deleted
   561  			file.Close()
   562  
   563  			tempErr := os.Remove(partialFilename)
   564  			if tempErr != nil && !os.IsNotExist(tempErr) {
   565  				NoticeWarning("reset partial download failed: %s", tempErr)
   566  			}
   567  
   568  			tempErr = os.Remove(partialETagFilename)
   569  			if tempErr != nil && !os.IsNotExist(tempErr) {
   570  				NoticeWarning("reset partial download ETag failed: %s", tempErr)
   571  			}
   572  
   573  			return 0, "", errors.Tracef(
   574  				"failed to load partial download ETag: %s", err)
   575  		}
   576  	}
   577  
   578  	request, err := http.NewRequest("GET", downloadURL, nil)
   579  	if err != nil {
   580  		return 0, "", errors.Trace(err)
   581  	}
   582  
   583  	request = request.WithContext(ctx)
   584  
   585  	request.Header.Set("User-Agent", userAgent)
   586  
   587  	request.Header.Add("Range", fmt.Sprintf("bytes=%d-", fileInfo.Size()))
   588  
   589  	if partialETag != nil {
   590  
   591  		// Note: not using If-Range, since not all host servers support it.
   592  		// Using If-Match means we need to check for status code 412 and reset
   593  		// when the ETag has changed since the last partial download.
   594  		request.Header.Add("If-Match", string(partialETag))
   595  
   596  	} else if ifNoneMatchETag != "" {
   597  
   598  		// Can't specify both If-Match and If-None-Match. Behavior is undefined.
   599  		// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
   600  		// So for downloaders that store an ETag and wish to use that to prevent
   601  		// redundant downloads, that ETag is sent as If-None-Match in the case
   602  		// where a partial download is not in progress. When a partial download
   603  		// is in progress, the partial ETag is sent as If-Match: either that's
   604  		// a version that was never fully received, or it's no longer current in
   605  		// which case the response will be StatusPreconditionFailed, the partial
   606  		// download will be discarded, and then the next retry will use
   607  		// If-None-Match.
   608  
   609  		// Note: in this case, fileInfo.Size() == 0
   610  
   611  		request.Header.Add("If-None-Match", ifNoneMatchETag)
   612  	}
   613  
   614  	response, err := httpClient.Do(request)
   615  
   616  	// The resumeable download may ask for bytes past the resource range
   617  	// since it doesn't store the "completed download" state. In this case,
   618  	// the HTTP server returns 416. Otherwise, we expect 206. We may also
   619  	// receive 412 on ETag mismatch.
   620  	if err == nil &&
   621  		(response.StatusCode != http.StatusPartialContent &&
   622  
   623  			// Certain http servers return 200 OK where we expect 206, so accept that.
   624  			response.StatusCode != http.StatusOK &&
   625  
   626  			response.StatusCode != http.StatusRequestedRangeNotSatisfiable &&
   627  			response.StatusCode != http.StatusPreconditionFailed &&
   628  			response.StatusCode != http.StatusNotModified) {
   629  		response.Body.Close()
   630  		err = fmt.Errorf("unexpected response status code: %d", response.StatusCode)
   631  	}
   632  	if err != nil {
   633  
   634  		// Redact URL from "net/http" error message.
   635  		if !GetEmitNetworkParameters() {
   636  			errStr := err.Error()
   637  			err = std_errors.New(strings.Replace(errStr, downloadURL, "[redacted]", -1))
   638  		}
   639  
   640  		return 0, "", errors.Trace(err)
   641  	}
   642  	defer response.Body.Close()
   643  
   644  	responseETag := response.Header.Get("ETag")
   645  
   646  	if response.StatusCode == http.StatusPreconditionFailed {
   647  		// When the ETag no longer matches, delete the partial download. As above,
   648  		// simply failing and relying on the caller's retry schedule.
   649  		os.Remove(partialFilename)
   650  		os.Remove(partialETagFilename)
   651  		return 0, "", errors.TraceNew("partial download ETag mismatch")
   652  
   653  	} else if response.StatusCode == http.StatusNotModified {
   654  		// This status code is possible in the "If-None-Match" case. Don't leave
   655  		// any partial download in progress. Caller should check that responseETag
   656  		// matches ifNoneMatchETag.
   657  		os.Remove(partialFilename)
   658  		os.Remove(partialETagFilename)
   659  		return 0, responseETag, nil
   660  	}
   661  
   662  	// Not making failure to write ETag file fatal, in case the entire download
   663  	// succeeds in this one request.
   664  	ioutil.WriteFile(partialETagFilename, []byte(responseETag), 0600)
   665  
   666  	// A partial download occurs when this copy is interrupted. The io.Copy
   667  	// will fail, leaving a partial download in place (.part and .part.etag).
   668  	n, err := io.Copy(NewSyncFileWriter(file), response.Body)
   669  
   670  	// From this point, n bytes are indicated as downloaded, even if there is
   671  	// an error; the caller may use this to report partial download progress.
   672  
   673  	if err != nil {
   674  		return n, "", errors.Trace(err)
   675  	}
   676  
   677  	// Ensure the file is flushed to disk. The deferred close
   678  	// will be a noop when this succeeds.
   679  	err = file.Close()
   680  	if err != nil {
   681  		return n, "", errors.Trace(err)
   682  	}
   683  
   684  	// Remove if exists, to enable rename
   685  	os.Remove(downloadFilename)
   686  
   687  	err = os.Rename(partialFilename, downloadFilename)
   688  	if err != nil {
   689  		return n, "", errors.Trace(err)
   690  	}
   691  
   692  	os.Remove(partialETagFilename)
   693  
   694  	return n, responseETag, nil
   695  }