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

     1  /*
     2   * Copyright (c) 2016, 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 server
    21  
    22  import (
    23  	"context"
    24  	"encoding/base64"
    25  	"encoding/json"
    26  	std_errors "errors"
    27  	"flag"
    28  	"fmt"
    29  	"io/ioutil"
    30  	"net"
    31  	"net/http"
    32  	"net/url"
    33  	"os"
    34  	"path/filepath"
    35  	"reflect"
    36  	"regexp"
    37  	"strconv"
    38  	"strings"
    39  	"sync"
    40  	"syscall"
    41  	"testing"
    42  	"time"
    43  
    44  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
    45  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
    46  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/accesscontrol"
    47  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
    48  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
    49  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
    50  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
    51  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/quic"
    52  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/tactics"
    53  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/values"
    54  	"github.com/miekg/dns"
    55  	"golang.org/x/net/proxy"
    56  )
    57  
    58  var serverIPAddress, testDataDirName string
    59  var mockWebServerURL, mockWebServerExpectedResponse string
    60  var mockWebServerPort = "8080"
    61  
    62  func TestMain(m *testing.M) {
    63  	flag.Parse()
    64  
    65  	serverIPv4Address, serverIPv6Address, err := common.GetRoutableInterfaceIPAddresses()
    66  	if err != nil {
    67  		fmt.Printf("error getting server IP address: %s\n", err)
    68  		os.Exit(1)
    69  	}
    70  	if serverIPv4Address != nil {
    71  		serverIPAddress = serverIPv4Address.String()
    72  	} else {
    73  		serverIPAddress = serverIPv6Address.String()
    74  	}
    75  
    76  	testDataDirName, err = ioutil.TempDir("", "psiphon-server-test")
    77  	if err != nil {
    78  		fmt.Printf("TempDir failed: %s\n", err)
    79  		os.Exit(1)
    80  	}
    81  	defer os.RemoveAll(testDataDirName)
    82  
    83  	psiphon.SetEmitDiagnosticNotices(true, true)
    84  
    85  	mockWebServerURL, mockWebServerExpectedResponse = runMockWebServer()
    86  
    87  	os.Exit(m.Run())
    88  }
    89  
    90  func runMockWebServer() (string, string) {
    91  
    92  	responseBody := prng.HexString(100000)
    93  
    94  	serveMux := http.NewServeMux()
    95  	serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    96  		w.Write([]byte(responseBody))
    97  	})
    98  	webServerAddress := net.JoinHostPort(serverIPAddress, mockWebServerPort)
    99  	server := &http.Server{
   100  		Addr:    webServerAddress,
   101  		Handler: serveMux,
   102  	}
   103  
   104  	go func() {
   105  		err := server.ListenAndServe()
   106  		if err != nil {
   107  			fmt.Printf("error running mock web server: %s\n", err)
   108  			os.Exit(1)
   109  		}
   110  	}()
   111  
   112  	// TODO: properly synchronize with web server readiness
   113  	time.Sleep(1 * time.Second)
   114  
   115  	return fmt.Sprintf("http://%s/", webServerAddress), responseBody
   116  }
   117  
   118  // Note: not testing fronted meek protocols, which client is
   119  // hard-wired to expect running on privileged ports 80 and 443.
   120  
   121  func TestSSH(t *testing.T) {
   122  	runServer(t,
   123  		&runServerConfig{
   124  			tunnelProtocol:       "SSH",
   125  			enableSSHAPIRequests: true,
   126  			doHotReload:          false,
   127  			doDefaultSponsorID:   false,
   128  			denyTrafficRules:     false,
   129  			requireAuthorization: true,
   130  			omitAuthorization:    false,
   131  			doTunneledWebRequest: true,
   132  			doTunneledNTPRequest: true,
   133  			forceFragmenting:     false,
   134  			forceLivenessTest:    false,
   135  			doPruneServerEntries: false,
   136  			doDanglingTCPConn:    true,
   137  			doPacketManipulation: false,
   138  			doBurstMonitor:       false,
   139  			doSplitTunnel:        false,
   140  			limitQUICVersions:    false,
   141  			doDestinationBytes:   false,
   142  			doChangeBytesConfig:  false,
   143  		})
   144  }
   145  
   146  func TestOSSH(t *testing.T) {
   147  	runServer(t,
   148  		&runServerConfig{
   149  			tunnelProtocol:       "OSSH",
   150  			enableSSHAPIRequests: true,
   151  			doHotReload:          false,
   152  			doDefaultSponsorID:   false,
   153  			denyTrafficRules:     false,
   154  			requireAuthorization: true,
   155  			omitAuthorization:    false,
   156  			doTunneledWebRequest: true,
   157  			doTunneledNTPRequest: true,
   158  			forceFragmenting:     false,
   159  			forceLivenessTest:    false,
   160  			doPruneServerEntries: false,
   161  			doDanglingTCPConn:    true,
   162  			doPacketManipulation: false,
   163  			doBurstMonitor:       false,
   164  			doSplitTunnel:        false,
   165  			limitQUICVersions:    false,
   166  			doDestinationBytes:   false,
   167  			doChangeBytesConfig:  false,
   168  		})
   169  }
   170  
   171  func TestFragmentedOSSH(t *testing.T) {
   172  	runServer(t,
   173  		&runServerConfig{
   174  			tunnelProtocol:       "OSSH",
   175  			enableSSHAPIRequests: true,
   176  			doHotReload:          false,
   177  			doDefaultSponsorID:   false,
   178  			denyTrafficRules:     false,
   179  			requireAuthorization: true,
   180  			omitAuthorization:    false,
   181  			doTunneledWebRequest: true,
   182  			doTunneledNTPRequest: true,
   183  			forceFragmenting:     true,
   184  			forceLivenessTest:    false,
   185  			doPruneServerEntries: false,
   186  			doDanglingTCPConn:    true,
   187  			doPacketManipulation: false,
   188  			doBurstMonitor:       false,
   189  			doSplitTunnel:        false,
   190  			limitQUICVersions:    false,
   191  			doDestinationBytes:   false,
   192  			doChangeBytesConfig:  false,
   193  		})
   194  }
   195  
   196  func TestUnfrontedMeek(t *testing.T) {
   197  	runServer(t,
   198  		&runServerConfig{
   199  			tunnelProtocol:       "UNFRONTED-MEEK-OSSH",
   200  			enableSSHAPIRequests: true,
   201  			doHotReload:          false,
   202  			doDefaultSponsorID:   false,
   203  			denyTrafficRules:     false,
   204  			requireAuthorization: true,
   205  			omitAuthorization:    false,
   206  			doTunneledWebRequest: true,
   207  			doTunneledNTPRequest: true,
   208  			forceFragmenting:     false,
   209  			forceLivenessTest:    false,
   210  			doPruneServerEntries: false,
   211  			doDanglingTCPConn:    true,
   212  			doPacketManipulation: false,
   213  			doBurstMonitor:       false,
   214  			doSplitTunnel:        false,
   215  			limitQUICVersions:    false,
   216  			doDestinationBytes:   false,
   217  			doChangeBytesConfig:  false,
   218  		})
   219  }
   220  
   221  func TestFragmentedUnfrontedMeek(t *testing.T) {
   222  	runServer(t,
   223  		&runServerConfig{
   224  			tunnelProtocol:       "UNFRONTED-MEEK-OSSH",
   225  			enableSSHAPIRequests: true,
   226  			doHotReload:          false,
   227  			doDefaultSponsorID:   false,
   228  			denyTrafficRules:     false,
   229  			requireAuthorization: true,
   230  			omitAuthorization:    false,
   231  			doTunneledWebRequest: true,
   232  			doTunneledNTPRequest: true,
   233  			forceFragmenting:     true,
   234  			forceLivenessTest:    false,
   235  			doPruneServerEntries: false,
   236  			doDanglingTCPConn:    true,
   237  			doPacketManipulation: false,
   238  			doBurstMonitor:       false,
   239  			doSplitTunnel:        false,
   240  			limitQUICVersions:    false,
   241  			doDestinationBytes:   false,
   242  			doChangeBytesConfig:  false,
   243  		})
   244  }
   245  
   246  func TestUnfrontedMeekHTTPS(t *testing.T) {
   247  	runServer(t,
   248  		&runServerConfig{
   249  			tunnelProtocol:       "UNFRONTED-MEEK-HTTPS-OSSH",
   250  			tlsProfile:           protocol.TLS_PROFILE_RANDOMIZED,
   251  			enableSSHAPIRequests: true,
   252  			doHotReload:          false,
   253  			doDefaultSponsorID:   false,
   254  			denyTrafficRules:     false,
   255  			requireAuthorization: true,
   256  			omitAuthorization:    false,
   257  			doTunneledWebRequest: true,
   258  			doTunneledNTPRequest: true,
   259  			forceFragmenting:     false,
   260  			forceLivenessTest:    false,
   261  			doPruneServerEntries: false,
   262  			doDanglingTCPConn:    true,
   263  			doPacketManipulation: false,
   264  			doBurstMonitor:       false,
   265  			doSplitTunnel:        false,
   266  			limitQUICVersions:    false,
   267  			doDestinationBytes:   false,
   268  			doChangeBytesConfig:  false,
   269  		})
   270  }
   271  
   272  func TestFragmentedUnfrontedMeekHTTPS(t *testing.T) {
   273  	runServer(t,
   274  		&runServerConfig{
   275  			tunnelProtocol:       "UNFRONTED-MEEK-HTTPS-OSSH",
   276  			tlsProfile:           protocol.TLS_PROFILE_RANDOMIZED,
   277  			enableSSHAPIRequests: true,
   278  			doHotReload:          false,
   279  			doDefaultSponsorID:   false,
   280  			denyTrafficRules:     false,
   281  			requireAuthorization: true,
   282  			omitAuthorization:    false,
   283  			doTunneledWebRequest: true,
   284  			doTunneledNTPRequest: true,
   285  			forceFragmenting:     true,
   286  			forceLivenessTest:    false,
   287  			doPruneServerEntries: false,
   288  			doDanglingTCPConn:    true,
   289  			doPacketManipulation: false,
   290  			doBurstMonitor:       false,
   291  			doSplitTunnel:        false,
   292  			limitQUICVersions:    false,
   293  			doDestinationBytes:   false,
   294  			doChangeBytesConfig:  false,
   295  		})
   296  }
   297  
   298  func TestUnfrontedMeekHTTPSTLS13(t *testing.T) {
   299  	runServer(t,
   300  		&runServerConfig{
   301  			tunnelProtocol:       "UNFRONTED-MEEK-HTTPS-OSSH",
   302  			tlsProfile:           protocol.TLS_PROFILE_CHROME_70,
   303  			enableSSHAPIRequests: true,
   304  			doHotReload:          false,
   305  			doDefaultSponsorID:   false,
   306  			denyTrafficRules:     false,
   307  			requireAuthorization: true,
   308  			omitAuthorization:    false,
   309  			doTunneledWebRequest: true,
   310  			doTunneledNTPRequest: true,
   311  			forceFragmenting:     false,
   312  			forceLivenessTest:    false,
   313  			doPruneServerEntries: false,
   314  			doDanglingTCPConn:    true,
   315  			doPacketManipulation: false,
   316  			doBurstMonitor:       false,
   317  			doSplitTunnel:        false,
   318  			limitQUICVersions:    false,
   319  			doDestinationBytes:   false,
   320  			doChangeBytesConfig:  false,
   321  		})
   322  }
   323  
   324  func TestUnfrontedMeekSessionTicket(t *testing.T) {
   325  	runServer(t,
   326  		&runServerConfig{
   327  			tunnelProtocol:       "UNFRONTED-MEEK-SESSION-TICKET-OSSH",
   328  			tlsProfile:           protocol.TLS_PROFILE_CHROME_58,
   329  			enableSSHAPIRequests: true,
   330  			doHotReload:          false,
   331  			doDefaultSponsorID:   false,
   332  			denyTrafficRules:     false,
   333  			requireAuthorization: true,
   334  			omitAuthorization:    false,
   335  			doTunneledWebRequest: true,
   336  			doTunneledNTPRequest: true,
   337  			forceFragmenting:     false,
   338  			forceLivenessTest:    false,
   339  			doPruneServerEntries: false,
   340  			doDanglingTCPConn:    true,
   341  			doPacketManipulation: false,
   342  			doBurstMonitor:       false,
   343  			doSplitTunnel:        false,
   344  			limitQUICVersions:    false,
   345  			doDestinationBytes:   false,
   346  			doChangeBytesConfig:  false,
   347  		})
   348  }
   349  
   350  func TestUnfrontedMeekSessionTicketTLS13(t *testing.T) {
   351  	runServer(t,
   352  		&runServerConfig{
   353  			tunnelProtocol:       "UNFRONTED-MEEK-SESSION-TICKET-OSSH",
   354  			tlsProfile:           protocol.TLS_PROFILE_CHROME_70,
   355  			enableSSHAPIRequests: true,
   356  			doHotReload:          false,
   357  			doDefaultSponsorID:   false,
   358  			denyTrafficRules:     false,
   359  			requireAuthorization: true,
   360  			omitAuthorization:    false,
   361  			doTunneledWebRequest: true,
   362  			doTunneledNTPRequest: true,
   363  			forceFragmenting:     false,
   364  			forceLivenessTest:    false,
   365  			doPruneServerEntries: false,
   366  			doDanglingTCPConn:    true,
   367  			doPacketManipulation: false,
   368  			doBurstMonitor:       false,
   369  			doSplitTunnel:        false,
   370  			limitQUICVersions:    false,
   371  			doDestinationBytes:   false,
   372  			doChangeBytesConfig:  false,
   373  		})
   374  }
   375  
   376  func TestQUICOSSH(t *testing.T) {
   377  	if !quic.Enabled() {
   378  		t.Skip("QUIC is not enabled")
   379  	}
   380  	runServer(t,
   381  		&runServerConfig{
   382  			tunnelProtocol:       "QUIC-OSSH",
   383  			enableSSHAPIRequests: true,
   384  			doHotReload:          false,
   385  			doDefaultSponsorID:   false,
   386  			denyTrafficRules:     false,
   387  			requireAuthorization: true,
   388  			omitAuthorization:    false,
   389  			doTunneledWebRequest: true,
   390  			doTunneledNTPRequest: true,
   391  			forceFragmenting:     false,
   392  			forceLivenessTest:    false,
   393  			doPruneServerEntries: false,
   394  			doDanglingTCPConn:    false,
   395  			doPacketManipulation: false,
   396  			doBurstMonitor:       false,
   397  			doSplitTunnel:        false,
   398  			limitQUICVersions:    false,
   399  			doDestinationBytes:   false,
   400  			doChangeBytesConfig:  false,
   401  		})
   402  }
   403  
   404  func TestLimitedQUICOSSH(t *testing.T) {
   405  	if !quic.Enabled() {
   406  		t.Skip("QUIC is not enabled")
   407  	}
   408  	runServer(t,
   409  		&runServerConfig{
   410  			tunnelProtocol:       "QUIC-OSSH",
   411  			enableSSHAPIRequests: true,
   412  			doHotReload:          false,
   413  			doDefaultSponsorID:   false,
   414  			denyTrafficRules:     false,
   415  			requireAuthorization: true,
   416  			omitAuthorization:    false,
   417  			doTunneledWebRequest: true,
   418  			doTunneledNTPRequest: true,
   419  			forceFragmenting:     false,
   420  			forceLivenessTest:    false,
   421  			doPruneServerEntries: false,
   422  			doDanglingTCPConn:    false,
   423  			doPacketManipulation: false,
   424  			doBurstMonitor:       false,
   425  			doSplitTunnel:        false,
   426  			limitQUICVersions:    true,
   427  			doDestinationBytes:   false,
   428  			doChangeBytesConfig:  false,
   429  		})
   430  }
   431  
   432  func TestWebTransportAPIRequests(t *testing.T) {
   433  	runServer(t,
   434  		&runServerConfig{
   435  			tunnelProtocol:       "OSSH",
   436  			enableSSHAPIRequests: false,
   437  			doHotReload:          false,
   438  			doDefaultSponsorID:   false,
   439  			denyTrafficRules:     false,
   440  			requireAuthorization: false,
   441  			omitAuthorization:    true,
   442  			doTunneledWebRequest: true,
   443  			doTunneledNTPRequest: true,
   444  			forceFragmenting:     false,
   445  			forceLivenessTest:    false,
   446  			doPruneServerEntries: false,
   447  			doDanglingTCPConn:    false,
   448  			doPacketManipulation: false,
   449  			doBurstMonitor:       false,
   450  			doSplitTunnel:        false,
   451  			limitQUICVersions:    false,
   452  			doDestinationBytes:   false,
   453  			doChangeBytesConfig:  false,
   454  		})
   455  }
   456  
   457  func TestHotReload(t *testing.T) {
   458  	runServer(t,
   459  		&runServerConfig{
   460  			tunnelProtocol:       "OSSH",
   461  			enableSSHAPIRequests: true,
   462  			doHotReload:          true,
   463  			doDefaultSponsorID:   false,
   464  			denyTrafficRules:     false,
   465  			requireAuthorization: true,
   466  			omitAuthorization:    false,
   467  			doTunneledWebRequest: true,
   468  			doTunneledNTPRequest: true,
   469  			forceFragmenting:     false,
   470  			forceLivenessTest:    false,
   471  			doPruneServerEntries: false,
   472  			doDanglingTCPConn:    false,
   473  			doPacketManipulation: false,
   474  			doBurstMonitor:       false,
   475  			doSplitTunnel:        false,
   476  			limitQUICVersions:    false,
   477  			doDestinationBytes:   false,
   478  			doChangeBytesConfig:  false,
   479  		})
   480  }
   481  
   482  func TestDefaultSponsorID(t *testing.T) {
   483  	runServer(t,
   484  		&runServerConfig{
   485  			tunnelProtocol:       "OSSH",
   486  			enableSSHAPIRequests: true,
   487  			doHotReload:          true,
   488  			doDefaultSponsorID:   true,
   489  			denyTrafficRules:     false,
   490  			requireAuthorization: true,
   491  			omitAuthorization:    false,
   492  			doTunneledWebRequest: true,
   493  			doTunneledNTPRequest: true,
   494  			forceFragmenting:     false,
   495  			forceLivenessTest:    false,
   496  			doPruneServerEntries: false,
   497  			doDanglingTCPConn:    false,
   498  			doPacketManipulation: false,
   499  			doBurstMonitor:       false,
   500  			doSplitTunnel:        false,
   501  			limitQUICVersions:    false,
   502  			doDestinationBytes:   false,
   503  			doChangeBytesConfig:  false,
   504  		})
   505  }
   506  
   507  func TestDenyTrafficRules(t *testing.T) {
   508  	runServer(t,
   509  		&runServerConfig{
   510  			tunnelProtocol:       "OSSH",
   511  			enableSSHAPIRequests: true,
   512  			doHotReload:          true,
   513  			doDefaultSponsorID:   false,
   514  			denyTrafficRules:     true,
   515  			requireAuthorization: true,
   516  			omitAuthorization:    false,
   517  			doTunneledWebRequest: true,
   518  			doTunneledNTPRequest: true,
   519  			forceFragmenting:     false,
   520  			forceLivenessTest:    false,
   521  			doPruneServerEntries: false,
   522  			doDanglingTCPConn:    false,
   523  			doPacketManipulation: false,
   524  			doBurstMonitor:       false,
   525  			doSplitTunnel:        false,
   526  			limitQUICVersions:    false,
   527  			doDestinationBytes:   false,
   528  			doChangeBytesConfig:  false,
   529  		})
   530  }
   531  
   532  func TestOmitAuthorization(t *testing.T) {
   533  	runServer(t,
   534  		&runServerConfig{
   535  			tunnelProtocol:       "OSSH",
   536  			enableSSHAPIRequests: true,
   537  			doHotReload:          true,
   538  			doDefaultSponsorID:   false,
   539  			denyTrafficRules:     false,
   540  			requireAuthorization: true,
   541  			omitAuthorization:    true,
   542  			doTunneledWebRequest: true,
   543  			doTunneledNTPRequest: true,
   544  			forceFragmenting:     false,
   545  			forceLivenessTest:    false,
   546  			doPruneServerEntries: false,
   547  			doDanglingTCPConn:    false,
   548  			doPacketManipulation: false,
   549  			doBurstMonitor:       false,
   550  			doSplitTunnel:        false,
   551  			limitQUICVersions:    false,
   552  			doDestinationBytes:   false,
   553  			doChangeBytesConfig:  false,
   554  		})
   555  }
   556  
   557  func TestNoAuthorization(t *testing.T) {
   558  	runServer(t,
   559  		&runServerConfig{
   560  			tunnelProtocol:       "OSSH",
   561  			enableSSHAPIRequests: true,
   562  			doHotReload:          true,
   563  			doDefaultSponsorID:   false,
   564  			denyTrafficRules:     false,
   565  			requireAuthorization: false,
   566  			omitAuthorization:    true,
   567  			doTunneledWebRequest: true,
   568  			doTunneledNTPRequest: true,
   569  			forceFragmenting:     false,
   570  			forceLivenessTest:    false,
   571  			doPruneServerEntries: false,
   572  			doDanglingTCPConn:    false,
   573  			doPacketManipulation: false,
   574  			doBurstMonitor:       false,
   575  			doSplitTunnel:        false,
   576  			limitQUICVersions:    false,
   577  			doDestinationBytes:   false,
   578  			doChangeBytesConfig:  false,
   579  		})
   580  }
   581  
   582  func TestUnusedAuthorization(t *testing.T) {
   583  	runServer(t,
   584  		&runServerConfig{
   585  			tunnelProtocol:       "OSSH",
   586  			enableSSHAPIRequests: true,
   587  			doHotReload:          true,
   588  			doDefaultSponsorID:   false,
   589  			denyTrafficRules:     false,
   590  			requireAuthorization: false,
   591  			omitAuthorization:    false,
   592  			doTunneledWebRequest: true,
   593  			doTunneledNTPRequest: true,
   594  			forceFragmenting:     false,
   595  			forceLivenessTest:    false,
   596  			doPruneServerEntries: false,
   597  			doDanglingTCPConn:    false,
   598  			doPacketManipulation: false,
   599  			doBurstMonitor:       false,
   600  			doSplitTunnel:        false,
   601  			limitQUICVersions:    false,
   602  			doDestinationBytes:   false,
   603  			doChangeBytesConfig:  false,
   604  		})
   605  }
   606  
   607  func TestTCPOnlySLOK(t *testing.T) {
   608  	runServer(t,
   609  		&runServerConfig{
   610  			tunnelProtocol:       "OSSH",
   611  			enableSSHAPIRequests: true,
   612  			doHotReload:          false,
   613  			doDefaultSponsorID:   false,
   614  			denyTrafficRules:     false,
   615  			requireAuthorization: true,
   616  			omitAuthorization:    false,
   617  			doTunneledWebRequest: true,
   618  			doTunneledNTPRequest: false,
   619  			forceFragmenting:     false,
   620  			forceLivenessTest:    false,
   621  			doPruneServerEntries: false,
   622  			doDanglingTCPConn:    false,
   623  			doPacketManipulation: false,
   624  			doBurstMonitor:       false,
   625  			doSplitTunnel:        false,
   626  			limitQUICVersions:    false,
   627  			doDestinationBytes:   false,
   628  			doChangeBytesConfig:  false,
   629  		})
   630  }
   631  
   632  func TestUDPOnlySLOK(t *testing.T) {
   633  	runServer(t,
   634  		&runServerConfig{
   635  			tunnelProtocol:       "OSSH",
   636  			enableSSHAPIRequests: true,
   637  			doHotReload:          false,
   638  			doDefaultSponsorID:   false,
   639  			denyTrafficRules:     false,
   640  			requireAuthorization: true,
   641  			omitAuthorization:    false,
   642  			doTunneledWebRequest: false,
   643  			doTunneledNTPRequest: true,
   644  			forceFragmenting:     false,
   645  			forceLivenessTest:    false,
   646  			doPruneServerEntries: false,
   647  			doDanglingTCPConn:    false,
   648  			doPacketManipulation: false,
   649  			doBurstMonitor:       false,
   650  			doSplitTunnel:        false,
   651  			limitQUICVersions:    false,
   652  			doDestinationBytes:   false,
   653  			doChangeBytesConfig:  false,
   654  		})
   655  }
   656  
   657  func TestLivenessTest(t *testing.T) {
   658  	runServer(t,
   659  		&runServerConfig{
   660  			tunnelProtocol:       "OSSH",
   661  			enableSSHAPIRequests: true,
   662  			doHotReload:          false,
   663  			doDefaultSponsorID:   false,
   664  			denyTrafficRules:     false,
   665  			requireAuthorization: true,
   666  			omitAuthorization:    false,
   667  			doTunneledWebRequest: true,
   668  			doTunneledNTPRequest: true,
   669  			forceFragmenting:     false,
   670  			forceLivenessTest:    true,
   671  			doPruneServerEntries: false,
   672  			doDanglingTCPConn:    false,
   673  			doPacketManipulation: false,
   674  			doBurstMonitor:       false,
   675  			doSplitTunnel:        false,
   676  			limitQUICVersions:    false,
   677  			doDestinationBytes:   false,
   678  			doChangeBytesConfig:  false,
   679  		})
   680  }
   681  
   682  func TestPruneServerEntries(t *testing.T) {
   683  	runServer(t,
   684  		&runServerConfig{
   685  			tunnelProtocol:       "OSSH",
   686  			enableSSHAPIRequests: true,
   687  			doHotReload:          false,
   688  			doDefaultSponsorID:   false,
   689  			denyTrafficRules:     false,
   690  			requireAuthorization: true,
   691  			omitAuthorization:    false,
   692  			doTunneledWebRequest: true,
   693  			doTunneledNTPRequest: true,
   694  			forceFragmenting:     false,
   695  			forceLivenessTest:    true,
   696  			doPruneServerEntries: true,
   697  			doDanglingTCPConn:    false,
   698  			doPacketManipulation: false,
   699  			doBurstMonitor:       false,
   700  			doSplitTunnel:        false,
   701  			limitQUICVersions:    false,
   702  			doDestinationBytes:   false,
   703  			doChangeBytesConfig:  false,
   704  		})
   705  }
   706  
   707  func TestBurstMonitorAndDestinationBytes(t *testing.T) {
   708  	runServer(t,
   709  		&runServerConfig{
   710  			tunnelProtocol:       "OSSH",
   711  			enableSSHAPIRequests: true,
   712  			doHotReload:          false,
   713  			doDefaultSponsorID:   false,
   714  			denyTrafficRules:     false,
   715  			requireAuthorization: true,
   716  			omitAuthorization:    false,
   717  			doTunneledWebRequest: true,
   718  			doTunneledNTPRequest: true,
   719  			forceFragmenting:     false,
   720  			forceLivenessTest:    false,
   721  			doPruneServerEntries: false,
   722  			doDanglingTCPConn:    true,
   723  			doPacketManipulation: false,
   724  			doBurstMonitor:       true,
   725  			doSplitTunnel:        false,
   726  			limitQUICVersions:    false,
   727  			doDestinationBytes:   true,
   728  			doChangeBytesConfig:  false,
   729  		})
   730  }
   731  
   732  func TestChangeBytesConfig(t *testing.T) {
   733  	runServer(t,
   734  		&runServerConfig{
   735  			tunnelProtocol:       "OSSH",
   736  			enableSSHAPIRequests: true,
   737  			doHotReload:          false,
   738  			doDefaultSponsorID:   false,
   739  			denyTrafficRules:     false,
   740  			requireAuthorization: true,
   741  			omitAuthorization:    false,
   742  			doTunneledWebRequest: true,
   743  			doTunneledNTPRequest: true,
   744  			forceFragmenting:     false,
   745  			forceLivenessTest:    false,
   746  			doPruneServerEntries: false,
   747  			doDanglingTCPConn:    true,
   748  			doPacketManipulation: false,
   749  			doBurstMonitor:       false,
   750  			doSplitTunnel:        false,
   751  			limitQUICVersions:    false,
   752  			doDestinationBytes:   true,
   753  			doChangeBytesConfig:  true,
   754  		})
   755  }
   756  
   757  func TestSplitTunnel(t *testing.T) {
   758  	runServer(t,
   759  		&runServerConfig{
   760  			tunnelProtocol:       "OSSH",
   761  			enableSSHAPIRequests: true,
   762  			doHotReload:          false,
   763  			doDefaultSponsorID:   false,
   764  			denyTrafficRules:     false,
   765  			requireAuthorization: true,
   766  			omitAuthorization:    false,
   767  			doTunneledWebRequest: true,
   768  			doTunneledNTPRequest: true,
   769  			forceFragmenting:     false,
   770  			forceLivenessTest:    false,
   771  			doPruneServerEntries: false,
   772  			doDanglingTCPConn:    true,
   773  			doPacketManipulation: false,
   774  			doBurstMonitor:       false,
   775  			doSplitTunnel:        true,
   776  			limitQUICVersions:    false,
   777  			doDestinationBytes:   false,
   778  			doChangeBytesConfig:  false,
   779  		})
   780  }
   781  
   782  type runServerConfig struct {
   783  	tunnelProtocol       string
   784  	tlsProfile           string
   785  	enableSSHAPIRequests bool
   786  	doHotReload          bool
   787  	doDefaultSponsorID   bool
   788  	denyTrafficRules     bool
   789  	requireAuthorization bool
   790  	omitAuthorization    bool
   791  	doTunneledWebRequest bool
   792  	doTunneledNTPRequest bool
   793  	forceFragmenting     bool
   794  	forceLivenessTest    bool
   795  	doPruneServerEntries bool
   796  	doDanglingTCPConn    bool
   797  	doPacketManipulation bool
   798  	doBurstMonitor       bool
   799  	doSplitTunnel        bool
   800  	limitQUICVersions    bool
   801  	doDestinationBytes   bool
   802  	doChangeBytesConfig  bool
   803  }
   804  
   805  var (
   806  	testSSHClientVersions                = []string{"SSH-2.0-A", "SSH-2.0-B", "SSH-2.0-C"}
   807  	testUserAgents                       = []string{"ua1", "ua2", "ua3"}
   808  	testNetworkType                      = "WIFI"
   809  	testCustomHostNameRegex              = `[a-z0-9]{5,10}\.example\.org`
   810  	testClientFeatures                   = []string{"feature 1", "feature 2"}
   811  	testDisallowedTrafficAlertActionURLs = []string{"https://example.org/disallowed"}
   812  )
   813  
   814  var serverRuns = 0
   815  
   816  func runServer(t *testing.T, runConfig *runServerConfig) {
   817  
   818  	serverRuns += 1
   819  
   820  	// configure authorized access
   821  
   822  	accessType := "test-access-type"
   823  
   824  	accessControlSigningKey, accessControlVerificationKey, err := accesscontrol.NewKeyPair(accessType)
   825  	if err != nil {
   826  		t.Fatalf("error creating access control key pair: %s", err)
   827  	}
   828  
   829  	accessControlVerificationKeyRing := accesscontrol.VerificationKeyRing{
   830  		Keys: []*accesscontrol.VerificationKey{accessControlVerificationKey},
   831  	}
   832  
   833  	var seedAuthorizationID [32]byte
   834  
   835  	clientAuthorization, authorizationID, err := accesscontrol.IssueAuthorization(
   836  		accessControlSigningKey,
   837  		seedAuthorizationID[:],
   838  		time.Now().Add(1*time.Hour))
   839  	if err != nil {
   840  		t.Fatalf("error issuing authorization: %s", err)
   841  	}
   842  
   843  	authorizationIDStr := base64.StdEncoding.EncodeToString(authorizationID)
   844  
   845  	// Enable tactics when the test protocol is meek. Both the client and the
   846  	// server will be configured to support tactics. The client config will be
   847  	// set with a nonfunctional config so that the tactics request must
   848  	// succeed, overriding the nonfunctional values, for the tunnel to
   849  	// establish.
   850  
   851  	doClientTactics := protocol.TunnelProtocolUsesMeek(runConfig.tunnelProtocol)
   852  	doServerTactics := doClientTactics ||
   853  		runConfig.forceFragmenting ||
   854  		runConfig.doBurstMonitor ||
   855  		runConfig.doDestinationBytes
   856  
   857  	// All servers require a tactics config with valid keys.
   858  	tacticsRequestPublicKey, tacticsRequestPrivateKey, tacticsRequestObfuscatedKey, err :=
   859  		tactics.GenerateKeys()
   860  	if err != nil {
   861  		t.Fatalf("error generating tactics keys: %s", err)
   862  	}
   863  
   864  	livenessTestSize := 0
   865  	if doClientTactics || runConfig.forceLivenessTest {
   866  		livenessTestSize = 1048576
   867  	}
   868  
   869  	// create a server
   870  
   871  	psiphonServerIPAddress := serverIPAddress
   872  	if protocol.TunnelProtocolUsesQUIC(runConfig.tunnelProtocol) {
   873  		// Workaround for macOS firewall.
   874  		psiphonServerIPAddress = "127.0.0.1"
   875  	}
   876  	psiphonServerPort := 4000
   877  
   878  	var limitQUICVersions protocol.QUICVersions
   879  	if runConfig.limitQUICVersions {
   880  
   881  		// Limit the server entry to one specific QUICv1 version, and check
   882  		// that this is used (see expectQUICVersion below). This test case
   883  		// also exercises disabling gQUIC in the server config and
   884  		// using "QUICv1" as the server entry capability.
   885  
   886  		selectedQUICVersion := protocol.SupportedQUICv1Versions[prng.Intn(
   887  			len(protocol.SupportedQUICv1Versions))]
   888  		limitQUICVersions = protocol.QUICVersions{selectedQUICVersion}
   889  	}
   890  
   891  	generateConfigParams := &GenerateConfigParams{
   892  		ServerIPAddress:      psiphonServerIPAddress,
   893  		EnableSSHAPIRequests: runConfig.enableSSHAPIRequests,
   894  		WebServerPort:        8000,
   895  		TunnelProtocolPorts:  map[string]int{runConfig.tunnelProtocol: psiphonServerPort},
   896  		LimitQUICVersions:    limitQUICVersions,
   897  		EnableGQUIC:          !runConfig.limitQUICVersions,
   898  	}
   899  
   900  	if doServerTactics {
   901  		generateConfigParams.TacticsRequestPublicKey = tacticsRequestPublicKey
   902  		generateConfigParams.TacticsRequestObfuscatedKey = tacticsRequestObfuscatedKey
   903  	}
   904  
   905  	serverConfigJSON, _, _, _, encodedServerEntry, err := GenerateConfig(generateConfigParams)
   906  	if err != nil {
   907  		t.Fatalf("error generating server config: %s", err)
   908  	}
   909  
   910  	// customize server config
   911  
   912  	// Initialize prune server entry test cases and associated data to pave into psinet.
   913  	pruneServerEntryTestCases, psinetValidServerEntryTags, expectedNumPruneNotices :=
   914  		initializePruneServerEntriesTest(t, runConfig)
   915  
   916  	// Pave psinet with random values to test handshake homepages.
   917  	psinetFilename := filepath.Join(testDataDirName, "psinet.json")
   918  	sponsorID, expectedHomepageURL := pavePsinetDatabaseFile(
   919  		t, psinetFilename, "", runConfig.doDefaultSponsorID, true, psinetValidServerEntryTags)
   920  
   921  	// Pave OSL config for SLOK testing
   922  	oslConfigFilename := filepath.Join(testDataDirName, "osl_config.json")
   923  	propagationChannelID := paveOSLConfigFile(t, oslConfigFilename)
   924  
   925  	// Pave traffic rules file which exercises handshake parameter filtering. Client
   926  	// must handshake with specified sponsor ID in order to allow ports for tunneled
   927  	// requests.
   928  	trafficRulesFilename := filepath.Join(testDataDirName, "traffic_rules.json")
   929  	paveTrafficRulesFile(
   930  		t,
   931  		trafficRulesFilename,
   932  		propagationChannelID,
   933  		accessType,
   934  		authorizationIDStr,
   935  		runConfig.requireAuthorization,
   936  		runConfig.denyTrafficRules,
   937  		livenessTestSize)
   938  
   939  	var tacticsConfigFilename string
   940  
   941  	// Only pave the tactics config when tactics are required. This exercises the
   942  	// case where the tactics config is omitted.
   943  	if doServerTactics {
   944  		tacticsConfigFilename = filepath.Join(testDataDirName, "tactics_config.json")
   945  		paveTacticsConfigFile(
   946  			t,
   947  			tacticsConfigFilename,
   948  			tacticsRequestPublicKey,
   949  			tacticsRequestPrivateKey,
   950  			tacticsRequestObfuscatedKey,
   951  			runConfig.tunnelProtocol,
   952  			propagationChannelID,
   953  			livenessTestSize,
   954  			runConfig.doBurstMonitor,
   955  			runConfig.doDestinationBytes)
   956  	}
   957  
   958  	blocklistFilename := filepath.Join(testDataDirName, "blocklist.csv")
   959  	paveBlocklistFile(t, blocklistFilename)
   960  
   961  	var serverConfig map[string]interface{}
   962  	json.Unmarshal(serverConfigJSON, &serverConfig)
   963  
   964  	// The test GeoIP databases map all IPs to a single, non-"None" country
   965  	// and ASN.
   966  	//
   967  	// When split tunnel mode is enabled, this should cause port forwards to
   968  	// be untunneled. When split tunnel mode is not enabled, port forwards
   969  	// should be tunneled despite the country match.
   970  	//
   971  	// When destination bytes metrics are enabled, all traffic will map to the
   972  	// single ASN.
   973  	geoIPCityDatabaseFilename := filepath.Join(testDataDirName, "geoip_city_database.mmbd")
   974  	geoIPISPDatabaseFilename := filepath.Join(testDataDirName, "geoip_isp_database.mmbd")
   975  	paveGeoIPDatabaseFiles(t, geoIPCityDatabaseFilename, geoIPISPDatabaseFilename)
   976  	serverConfig["GeoIPDatabaseFilenames"] = []string{geoIPCityDatabaseFilename, geoIPISPDatabaseFilename}
   977  
   978  	serverConfig["PsinetDatabaseFilename"] = psinetFilename
   979  	serverConfig["TrafficRulesFilename"] = trafficRulesFilename
   980  	serverConfig["OSLConfigFilename"] = oslConfigFilename
   981  	if doServerTactics {
   982  		serverConfig["TacticsConfigFilename"] = tacticsConfigFilename
   983  	}
   984  	serverConfig["BlocklistFilename"] = blocklistFilename
   985  
   986  	serverConfig["LogFilename"] = filepath.Join(testDataDirName, "psiphond.log")
   987  	serverConfig["LogLevel"] = "debug"
   988  
   989  	serverConfig["AccessControlVerificationKeyRing"] = accessControlVerificationKeyRing
   990  
   991  	// Set this parameter so at least the semaphore functions are called.
   992  	// TODO: test that the concurrency limit is correctly enforced.
   993  	serverConfig["MaxConcurrentSSHHandshakes"] = 1
   994  
   995  	// Ensure peak failure rate log fields for a single port forward attempt
   996  	serverConfig["PeakUpstreamFailureRateMinimumSampleSize"] = 1
   997  
   998  	// Exercise this option.
   999  	serverConfig["PeriodicGarbageCollectionSeconds"] = 1
  1000  
  1001  	// Allow port forwards to local test web server.
  1002  	serverConfig["AllowBogons"] = true
  1003  
  1004  	serverConfig["RunPacketManipulator"] = runConfig.doPacketManipulation
  1005  
  1006  	if protocol.TunnelProtocolUsesQUIC(runConfig.tunnelProtocol) && quic.GQUICEnabled() {
  1007  		// Enable legacy QUIC version support.
  1008  		serverConfig["EnableGQUIC"] = true
  1009  	}
  1010  
  1011  	serverConfigJSON, _ = json.Marshal(serverConfig)
  1012  
  1013  	uniqueUserLog := make(chan map[string]interface{}, 1)
  1014  	domainBytesLog := make(chan map[string]interface{}, 1)
  1015  	serverTunnelLog := make(chan map[string]interface{}, 1)
  1016  
  1017  	setLogCallback(func(log []byte) {
  1018  
  1019  		logFields := make(map[string]interface{})
  1020  
  1021  		err := json.Unmarshal(log, &logFields)
  1022  		if err != nil {
  1023  			return
  1024  		}
  1025  
  1026  		if logFields["event_name"] == nil {
  1027  			return
  1028  		}
  1029  
  1030  		switch logFields["event_name"].(string) {
  1031  		case "unique_user":
  1032  			select {
  1033  			case uniqueUserLog <- logFields:
  1034  			default:
  1035  			}
  1036  		case "domain_bytes":
  1037  			select {
  1038  			case domainBytesLog <- logFields:
  1039  			default:
  1040  			}
  1041  		case "server_tunnel":
  1042  			select {
  1043  			case serverTunnelLog <- logFields:
  1044  			default:
  1045  			}
  1046  		}
  1047  	})
  1048  
  1049  	// run server
  1050  
  1051  	serverWaitGroup := new(sync.WaitGroup)
  1052  	serverWaitGroup.Add(1)
  1053  	go func() {
  1054  		defer serverWaitGroup.Done()
  1055  		err := RunServices(serverConfigJSON)
  1056  		if err != nil {
  1057  			// TODO: wrong goroutine for t.FatalNow()
  1058  			t.Errorf("error running server: %s", err)
  1059  		}
  1060  	}()
  1061  
  1062  	stopServer := func() {
  1063  
  1064  		// Test: orderly server shutdown
  1065  
  1066  		p, _ := os.FindProcess(os.Getpid())
  1067  		p.Signal(os.Interrupt)
  1068  
  1069  		shutdownTimeout := time.NewTimer(5 * time.Second)
  1070  
  1071  		shutdownOk := make(chan struct{}, 1)
  1072  		go func() {
  1073  			serverWaitGroup.Wait()
  1074  			shutdownOk <- struct{}{}
  1075  		}()
  1076  
  1077  		select {
  1078  		case <-shutdownOk:
  1079  		case <-shutdownTimeout.C:
  1080  			t.Errorf("server shutdown timeout exceeded")
  1081  		}
  1082  	}
  1083  
  1084  	// Stop server on early exits due to failure.
  1085  	defer func() {
  1086  		if stopServer != nil {
  1087  			stopServer()
  1088  		}
  1089  	}()
  1090  
  1091  	// TODO: monitor logs for more robust wait-until-loaded. For example,
  1092  	// especially with the race detector on, QUIC-OSSH tests can fail as the
  1093  	// client sends its initial packet before the server is ready.
  1094  	time.Sleep(1 * time.Second)
  1095  
  1096  	// Test: hot reload (of psinet and traffic rules)
  1097  
  1098  	if runConfig.doHotReload {
  1099  
  1100  		// Pave new config files with different random values.
  1101  		sponsorID, expectedHomepageURL = pavePsinetDatabaseFile(
  1102  			t, psinetFilename, "", runConfig.doDefaultSponsorID, true, psinetValidServerEntryTags)
  1103  
  1104  		propagationChannelID = paveOSLConfigFile(t, oslConfigFilename)
  1105  
  1106  		paveTrafficRulesFile(
  1107  			t,
  1108  			trafficRulesFilename,
  1109  			propagationChannelID,
  1110  			accessType,
  1111  			authorizationIDStr,
  1112  			runConfig.requireAuthorization,
  1113  			runConfig.denyTrafficRules,
  1114  			livenessTestSize)
  1115  
  1116  		p, _ := os.FindProcess(os.Getpid())
  1117  		p.Signal(syscall.SIGUSR1)
  1118  
  1119  		// TODO: monitor logs for more robust wait-until-reloaded
  1120  		time.Sleep(1 * time.Second)
  1121  
  1122  		// After reloading psinet, the new sponsorID/expectedHomepageURL
  1123  		// should be active, as tested in the client "Homepage" notice
  1124  		// handler below.
  1125  	}
  1126  
  1127  	// Exercise server_load logging
  1128  	p, _ := os.FindProcess(os.Getpid())
  1129  	p.Signal(syscall.SIGUSR2)
  1130  
  1131  	// configure client
  1132  
  1133  	values.SetSSHClientVersionsSpec(values.NewPickOneSpec(testSSHClientVersions))
  1134  
  1135  	values.SetUserAgentsSpec(values.NewPickOneSpec(testUserAgents))
  1136  
  1137  	// TODO: currently, TargetServerEntry only works with one tunnel
  1138  	numTunnels := 1
  1139  	localSOCKSProxyPort := 1081
  1140  	localHTTPProxyPort := 8081
  1141  
  1142  	// Use a distinct suffix for network ID for each test run to ensure tactics
  1143  	// from different runs don't apply; this is a workaround for the singleton
  1144  	// datastore.
  1145  	jsonNetworkID := fmt.Sprintf(`,"NetworkID" : "WIFI-%s"`, time.Now().String())
  1146  
  1147  	jsonLimitTLSProfiles := ""
  1148  	if runConfig.tlsProfile != "" {
  1149  		jsonLimitTLSProfiles = fmt.Sprintf(`,"LimitTLSProfiles" : ["%s"]`, runConfig.tlsProfile)
  1150  	}
  1151  
  1152  	testClientFeaturesJSON, _ := json.Marshal(testClientFeatures)
  1153  
  1154  	clientConfigJSON := fmt.Sprintf(`
  1155      {
  1156          "ClientPlatform" : "Android_10_com.test.app",
  1157          "ClientVersion" : "0",
  1158          "ClientFeatures" : %s,
  1159          "SponsorId" : "0",
  1160          "PropagationChannelId" : "0",
  1161          "DeviceRegion" : "US",
  1162          "DisableRemoteServerListFetcher" : true,
  1163          "EstablishTunnelPausePeriodSeconds" : 1,
  1164          "ConnectionWorkerPoolSize" : %d,
  1165          "LimitTunnelProtocols" : ["%s"]
  1166          %s
  1167          %s
  1168      }`,
  1169  		string(testClientFeaturesJSON),
  1170  		numTunnels,
  1171  		runConfig.tunnelProtocol,
  1172  		jsonLimitTLSProfiles,
  1173  		jsonNetworkID)
  1174  
  1175  	clientConfig, err := psiphon.LoadConfig([]byte(clientConfigJSON))
  1176  	if err != nil {
  1177  		t.Fatalf("error processing configuration file: %s", err)
  1178  	}
  1179  
  1180  	clientConfig.DataRootDirectory = testDataDirName
  1181  
  1182  	if !runConfig.doDefaultSponsorID {
  1183  		clientConfig.SponsorId = sponsorID
  1184  	}
  1185  	clientConfig.PropagationChannelId = propagationChannelID
  1186  	clientConfig.TunnelPoolSize = numTunnels
  1187  	clientConfig.TargetServerEntry = string(encodedServerEntry)
  1188  	clientConfig.LocalSocksProxyPort = localSOCKSProxyPort
  1189  	clientConfig.LocalHttpProxyPort = localHTTPProxyPort
  1190  	clientConfig.EmitSLOKs = true
  1191  	clientConfig.EmitServerAlerts = true
  1192  
  1193  	if runConfig.doSplitTunnel {
  1194  		clientConfig.SplitTunnelOwnRegion = true
  1195  	}
  1196  
  1197  	if !runConfig.omitAuthorization {
  1198  		clientConfig.Authorizations = []string{clientAuthorization}
  1199  	}
  1200  
  1201  	err = clientConfig.Commit(false)
  1202  	if err != nil {
  1203  		t.Fatalf("error committing configuration file: %s", err)
  1204  	}
  1205  
  1206  	if doClientTactics {
  1207  		// Configure nonfunctional values that must be overridden by tactics.
  1208  
  1209  		applyParameters := make(map[string]interface{})
  1210  
  1211  		applyParameters[parameters.TunnelConnectTimeout] = "1s"
  1212  		applyParameters[parameters.TunnelRateLimits] = common.RateLimits{WriteBytesPerSecond: 1}
  1213  
  1214  		err = clientConfig.SetParameters("", true, applyParameters)
  1215  		if err != nil {
  1216  			t.Fatalf("SetParameters failed: %s", err)
  1217  		}
  1218  
  1219  	} else {
  1220  
  1221  		// Directly apply same parameters that would've come from tactics.
  1222  
  1223  		applyParameters := make(map[string]interface{})
  1224  
  1225  		if runConfig.forceFragmenting {
  1226  			applyParameters[parameters.FragmentorLimitProtocols] = protocol.TunnelProtocols{runConfig.tunnelProtocol}
  1227  			applyParameters[parameters.FragmentorProbability] = 1.0
  1228  			applyParameters[parameters.FragmentorMinTotalBytes] = 1000
  1229  			applyParameters[parameters.FragmentorMaxTotalBytes] = 2000
  1230  			applyParameters[parameters.FragmentorMinWriteBytes] = 1
  1231  			applyParameters[parameters.FragmentorMaxWriteBytes] = 100
  1232  			applyParameters[parameters.FragmentorMinDelay] = 1 * time.Millisecond
  1233  			applyParameters[parameters.FragmentorMaxDelay] = 10 * time.Millisecond
  1234  		}
  1235  
  1236  		if runConfig.forceLivenessTest {
  1237  			applyParameters[parameters.LivenessTestMinUpstreamBytes] = livenessTestSize
  1238  			applyParameters[parameters.LivenessTestMaxUpstreamBytes] = livenessTestSize
  1239  			applyParameters[parameters.LivenessTestMinDownstreamBytes] = livenessTestSize
  1240  			applyParameters[parameters.LivenessTestMaxDownstreamBytes] = livenessTestSize
  1241  		}
  1242  
  1243  		if runConfig.doPruneServerEntries {
  1244  			applyParameters[parameters.PsiphonAPIStatusRequestShortPeriodMin] = 1 * time.Millisecond
  1245  			applyParameters[parameters.PsiphonAPIStatusRequestShortPeriodMax] = 1 * time.Millisecond
  1246  		}
  1247  
  1248  		err = clientConfig.SetParameters("", true, applyParameters)
  1249  		if err != nil {
  1250  			t.Fatalf("SetParameters failed: %s", err)
  1251  		}
  1252  	}
  1253  
  1254  	// connect to server with client
  1255  
  1256  	err = psiphon.OpenDataStore(clientConfig)
  1257  	if err != nil {
  1258  		t.Fatalf("error initializing client datastore: %s", err)
  1259  	}
  1260  	defer psiphon.CloseDataStore()
  1261  
  1262  	// Test unique user counting cases.
  1263  	var expectUniqueUser bool
  1264  	switch serverRuns % 3 {
  1265  	case 0:
  1266  		// Mock no last_connected.
  1267  		psiphon.SetKeyValue("lastConnected", "")
  1268  		expectUniqueUser = true
  1269  	case 1:
  1270  		// Mock previous day last_connected.
  1271  		psiphon.SetKeyValue(
  1272  			"lastConnected",
  1273  			time.Now().UTC().AddDate(0, 0, -1).Truncate(1*time.Hour).Format(time.RFC3339))
  1274  		expectUniqueUser = true
  1275  	case 2:
  1276  		// Leave previous last_connected.
  1277  		expectUniqueUser = false
  1278  	}
  1279  
  1280  	// Clear SLOKs from previous test runs.
  1281  	psiphon.DeleteSLOKs()
  1282  
  1283  	// Store prune server entry test server entries and failed tunnel records.
  1284  	storePruneServerEntriesTest(
  1285  		t, runConfig, testDataDirName, pruneServerEntryTestCases)
  1286  
  1287  	controller, err := psiphon.NewController(clientConfig)
  1288  	if err != nil {
  1289  		t.Fatalf("error creating client controller: %s", err)
  1290  	}
  1291  
  1292  	connectedServer := make(chan struct{}, 1)
  1293  	tunnelsEstablished := make(chan struct{}, 1)
  1294  	homepageReceived := make(chan struct{}, 1)
  1295  	slokSeeded := make(chan struct{}, 1)
  1296  	numPruneNotices := 0
  1297  	pruneServerEntriesNoticesEmitted := make(chan struct{}, 1)
  1298  	serverAlertDisallowedNoticesEmitted := make(chan struct{}, 1)
  1299  	untunneledPortForward := make(chan struct{}, 1)
  1300  
  1301  	psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver(
  1302  		func(notice []byte) {
  1303  
  1304  			//fmt.Printf("%s\n", string(notice))
  1305  
  1306  			noticeType, payload, err := psiphon.GetNotice(notice)
  1307  			if err != nil {
  1308  				return
  1309  			}
  1310  
  1311  			switch noticeType {
  1312  
  1313  			case "ConnectedServer":
  1314  				sendNotificationReceived(connectedServer)
  1315  
  1316  			case "Tunnels":
  1317  				count := int(payload["count"].(float64))
  1318  				if count >= numTunnels {
  1319  					sendNotificationReceived(tunnelsEstablished)
  1320  				}
  1321  
  1322  			case "Homepage":
  1323  				homepageURL := payload["url"].(string)
  1324  				if homepageURL != expectedHomepageURL {
  1325  					// TODO: wrong goroutine for t.FatalNow()
  1326  					t.Errorf("unexpected homepage: %s", homepageURL)
  1327  				}
  1328  				sendNotificationReceived(homepageReceived)
  1329  
  1330  			case "SLOKSeeded":
  1331  				sendNotificationReceived(slokSeeded)
  1332  
  1333  			case "PruneServerEntry":
  1334  				numPruneNotices += 1
  1335  				if numPruneNotices == expectedNumPruneNotices {
  1336  					sendNotificationReceived(pruneServerEntriesNoticesEmitted)
  1337  				}
  1338  
  1339  			case "ServerAlert":
  1340  
  1341  				reason := payload["reason"].(string)
  1342  				actionURLsPayload := payload["actionURLs"].([]interface{})
  1343  				actionURLs := make([]string, len(actionURLsPayload))
  1344  				for i, value := range actionURLsPayload {
  1345  					actionURLs[i] = value.(string)
  1346  				}
  1347  				if reason == protocol.PSIPHON_API_ALERT_DISALLOWED_TRAFFIC &&
  1348  					reflect.DeepEqual(actionURLs, testDisallowedTrafficAlertActionURLs) {
  1349  					sendNotificationReceived(serverAlertDisallowedNoticesEmitted)
  1350  				}
  1351  
  1352  			case "Untunneled":
  1353  				sendNotificationReceived(untunneledPortForward)
  1354  
  1355  			}
  1356  		}))
  1357  
  1358  	ctx, cancelFunc := context.WithCancel(context.Background())
  1359  
  1360  	controllerWaitGroup := new(sync.WaitGroup)
  1361  
  1362  	controllerWaitGroup.Add(1)
  1363  	go func() {
  1364  		defer controllerWaitGroup.Done()
  1365  		controller.Run(ctx)
  1366  	}()
  1367  
  1368  	stopClient := func() {
  1369  		cancelFunc()
  1370  
  1371  		shutdownTimeout := time.NewTimer(20 * time.Second)
  1372  
  1373  		shutdownOk := make(chan struct{}, 1)
  1374  		go func() {
  1375  			controllerWaitGroup.Wait()
  1376  			shutdownOk <- struct{}{}
  1377  		}()
  1378  
  1379  		select {
  1380  		case <-shutdownOk:
  1381  		case <-shutdownTimeout.C:
  1382  			t.Errorf("controller shutdown timeout exceeded")
  1383  		}
  1384  	}
  1385  
  1386  	// Stop client on early exits due to failure.
  1387  	defer func() {
  1388  		if stopClient != nil {
  1389  			stopClient()
  1390  		}
  1391  	}()
  1392  
  1393  	// Test: tunnels must be established, and correct homepage
  1394  	// must be received, within 30 seconds
  1395  
  1396  	timeoutSignal := make(chan struct{})
  1397  	go func() {
  1398  		timer := time.NewTimer(30 * time.Second)
  1399  		<-timer.C
  1400  		close(timeoutSignal)
  1401  	}()
  1402  
  1403  	waitOnNotification(t, connectedServer, timeoutSignal, "connected server timeout exceeded")
  1404  	waitOnNotification(t, tunnelsEstablished, timeoutSignal, "tunnel established timeout exceeded")
  1405  	waitOnNotification(t, homepageReceived, timeoutSignal, "homepage received timeout exceeded")
  1406  
  1407  	if runConfig.doChangeBytesConfig {
  1408  
  1409  		if !runConfig.doDestinationBytes {
  1410  			t.Fatalf("invalid test configuration")
  1411  		}
  1412  
  1413  		// Test: now that the client is connected, change the domain bytes and
  1414  		// destination bytes configurations. No stats should be logged, even
  1415  		// with an already connected client.
  1416  
  1417  		// Pave psinet without domain bytes; retain the same sponsor ID. The
  1418  		// random homepage URLs will change, but this has no effect on the
  1419  		// already connected client.
  1420  		_, _ = pavePsinetDatabaseFile(
  1421  			t, psinetFilename, sponsorID, runConfig.doDefaultSponsorID, false, psinetValidServerEntryTags)
  1422  
  1423  		// Pave tactics without destination bytes.
  1424  		paveTacticsConfigFile(
  1425  			t,
  1426  			tacticsConfigFilename,
  1427  			tacticsRequestPublicKey,
  1428  			tacticsRequestPrivateKey,
  1429  			tacticsRequestObfuscatedKey,
  1430  			runConfig.tunnelProtocol,
  1431  			propagationChannelID,
  1432  			livenessTestSize,
  1433  			runConfig.doBurstMonitor,
  1434  			false)
  1435  
  1436  		p, _ := os.FindProcess(os.Getpid())
  1437  		p.Signal(syscall.SIGUSR1)
  1438  
  1439  		// TODO: monitor logs for more robust wait-until-reloaded
  1440  		time.Sleep(1 * time.Second)
  1441  	}
  1442  
  1443  	expectTrafficFailure := runConfig.denyTrafficRules || (runConfig.omitAuthorization && runConfig.requireAuthorization)
  1444  
  1445  	// The client still reports zero domain_bytes when no port forwards are allowed (expectTrafficFailure)
  1446  	expectDomainBytes := !runConfig.doChangeBytesConfig
  1447  
  1448  	if runConfig.doTunneledWebRequest {
  1449  
  1450  		// Test: tunneled web site fetch
  1451  
  1452  		err = makeTunneledWebRequest(
  1453  			t, localHTTPProxyPort, mockWebServerURL, mockWebServerExpectedResponse)
  1454  
  1455  		if err == nil {
  1456  			if expectTrafficFailure {
  1457  				t.Fatalf("unexpected tunneled web request success")
  1458  			}
  1459  		} else {
  1460  			if !expectTrafficFailure {
  1461  				t.Fatalf("tunneled web request failed: %s", err)
  1462  			}
  1463  		}
  1464  	}
  1465  
  1466  	if runConfig.doTunneledNTPRequest {
  1467  
  1468  		// Test: tunneled UDP packets
  1469  
  1470  		udpgwServerAddress := serverConfig["UDPInterceptUdpgwServerAddress"].(string)
  1471  
  1472  		err = makeTunneledNTPRequest(t, localSOCKSProxyPort, udpgwServerAddress)
  1473  
  1474  		if err == nil {
  1475  			if expectTrafficFailure {
  1476  				t.Fatalf("unexpected tunneled NTP request success")
  1477  			}
  1478  		} else {
  1479  			if !expectTrafficFailure {
  1480  				t.Fatalf("tunneled NTP request failed: %s", err)
  1481  			}
  1482  		}
  1483  	}
  1484  
  1485  	// Test: await SLOK payload or server alert notice
  1486  
  1487  	time.Sleep(1 * time.Second)
  1488  
  1489  	if !expectTrafficFailure {
  1490  
  1491  		waitOnNotification(t, slokSeeded, timeoutSignal, "SLOK seeded timeout exceeded")
  1492  
  1493  		numSLOKs := psiphon.CountSLOKs()
  1494  		if numSLOKs != expectedNumSLOKs {
  1495  			t.Fatalf("unexpected number of SLOKs: %d", numSLOKs)
  1496  		}
  1497  
  1498  	} else {
  1499  
  1500  		// Note: in expectTrafficFailure case, timeoutSignal may have already fired.
  1501  
  1502  		waitOnNotification(t, serverAlertDisallowedNoticesEmitted, nil, "")
  1503  	}
  1504  
  1505  	// Test: await expected prune server entry notices
  1506  	//
  1507  	// Note: will take up to PsiphonAPIStatusRequestShortPeriodMax to emit.
  1508  
  1509  	if expectedNumPruneNotices > 0 {
  1510  		waitOnNotification(t, pruneServerEntriesNoticesEmitted, nil, "")
  1511  	}
  1512  
  1513  	if runConfig.doDanglingTCPConn {
  1514  
  1515  		// Test: client that has established TCP connection but not completed
  1516  		// any handshakes must not block/delay server shutdown
  1517  
  1518  		danglingConn, err := net.Dial(
  1519  			"tcp", net.JoinHostPort(psiphonServerIPAddress, strconv.Itoa(psiphonServerPort)))
  1520  		if err != nil {
  1521  			t.Fatalf("TCP dial failed: %s", err)
  1522  		}
  1523  		defer danglingConn.Close()
  1524  	}
  1525  
  1526  	// Test: check for split tunnel notice
  1527  
  1528  	if runConfig.doSplitTunnel {
  1529  		if !runConfig.doTunneledWebRequest || expectTrafficFailure {
  1530  			t.Fatalf("invalid test run configuration")
  1531  		}
  1532  		waitOnNotification(t, untunneledPortForward, nil, "")
  1533  	} else {
  1534  		// There should be no "Untunneled" notice. This check assumes that any
  1535  		// unexpected Untunneled notice will have been delivered at this point,
  1536  		// after the SLOK notice.
  1537  		select {
  1538  		case <-untunneledPortForward:
  1539  			t.Fatalf("unexpected untunnedl port forward")
  1540  		default:
  1541  		}
  1542  	}
  1543  
  1544  	// Trigger server_load logging once more, to exercise
  1545  	// sshClient.peakMetrics. As we don't have a reference to the server's
  1546  	// Support struct, we can't invoke logServerLoad directly and there's a
  1547  	// potential race between asynchronous logServerLoad invocation and
  1548  	// client shutdown. For now, we sleep as a workaround.
  1549  
  1550  	p.Signal(syscall.SIGUSR2)
  1551  	time.Sleep(1 * time.Second)
  1552  
  1553  	// Shutdown to ensure logs/notices are flushed
  1554  
  1555  	stopClient()
  1556  	stopClient = nil
  1557  	stopServer()
  1558  	stopServer = nil
  1559  
  1560  	// Test: all expected server logs were emitted
  1561  
  1562  	// TODO: stops should be fully synchronous, but, intermittently,
  1563  	// server_tunnel fails to appear ("missing server tunnel log")
  1564  	// without this delay.
  1565  	time.Sleep(100 * time.Millisecond)
  1566  
  1567  	expectClientBPFField := psiphon.ClientBPFEnabled() && doClientTactics
  1568  	expectServerBPFField := ServerBPFEnabled() && doServerTactics
  1569  	expectServerPacketManipulationField := runConfig.doPacketManipulation
  1570  	expectBurstFields := runConfig.doBurstMonitor
  1571  	expectTCPPortForwardDial := runConfig.doTunneledWebRequest
  1572  	expectTCPDataTransfer := runConfig.doTunneledWebRequest && !expectTrafficFailure && !runConfig.doSplitTunnel
  1573  	// Even with expectTrafficFailure, DNS port forwards will succeed
  1574  	expectUDPDataTransfer := runConfig.doTunneledNTPRequest
  1575  	expectQUICVersion := ""
  1576  	if runConfig.limitQUICVersions {
  1577  		expectQUICVersion = limitQUICVersions[0]
  1578  	}
  1579  	expectDestinationBytesFields := runConfig.doDestinationBytes && !runConfig.doChangeBytesConfig
  1580  
  1581  	select {
  1582  	case logFields := <-serverTunnelLog:
  1583  		err := checkExpectedServerTunnelLogFields(
  1584  			runConfig,
  1585  			expectClientBPFField,
  1586  			expectServerBPFField,
  1587  			expectServerPacketManipulationField,
  1588  			expectBurstFields,
  1589  			expectTCPPortForwardDial,
  1590  			expectTCPDataTransfer,
  1591  			expectUDPDataTransfer,
  1592  			expectQUICVersion,
  1593  			expectDestinationBytesFields,
  1594  			logFields)
  1595  		if err != nil {
  1596  			t.Fatalf("invalid server tunnel log fields: %s", err)
  1597  		}
  1598  	default:
  1599  		t.Fatalf("missing server tunnel log")
  1600  	}
  1601  
  1602  	if expectUniqueUser {
  1603  		select {
  1604  		case logFields := <-uniqueUserLog:
  1605  			err := checkExpectedUniqueUserLogFields(
  1606  				runConfig,
  1607  				logFields)
  1608  			if err != nil {
  1609  				t.Fatalf("invalid unique user log fields: %s", err)
  1610  			}
  1611  		default:
  1612  			t.Fatalf("missing unique user log")
  1613  		}
  1614  	} else {
  1615  		select {
  1616  		case <-uniqueUserLog:
  1617  			t.Fatalf("unexpected unique user log")
  1618  		default:
  1619  		}
  1620  	}
  1621  
  1622  	if expectDomainBytes {
  1623  		select {
  1624  		case logFields := <-domainBytesLog:
  1625  			err := checkExpectedDomainBytesLogFields(
  1626  				runConfig,
  1627  				logFields)
  1628  			if err != nil {
  1629  				t.Fatalf("invalid domain bytes log fields: %s", err)
  1630  			}
  1631  		default:
  1632  			t.Fatalf("missing domain bytes log")
  1633  		}
  1634  	} else {
  1635  		select {
  1636  		case <-domainBytesLog:
  1637  			t.Fatalf("unexpected domain bytes log")
  1638  		default:
  1639  		}
  1640  	}
  1641  
  1642  	// Check that datastore had retained/pruned server entries as expected.
  1643  	checkPruneServerEntriesTest(t, runConfig, testDataDirName, pruneServerEntryTestCases)
  1644  }
  1645  
  1646  func sendNotificationReceived(c chan<- struct{}) {
  1647  	select {
  1648  	case c <- struct{}{}:
  1649  	default:
  1650  	}
  1651  }
  1652  
  1653  func waitOnNotification(t *testing.T, c, timeoutSignal <-chan struct{}, timeoutMessage string) {
  1654  	if timeoutSignal == nil {
  1655  		<-c
  1656  	} else {
  1657  		select {
  1658  		case <-c:
  1659  		case <-timeoutSignal:
  1660  			t.Fatalf(timeoutMessage)
  1661  		}
  1662  	}
  1663  }
  1664  
  1665  func checkExpectedServerTunnelLogFields(
  1666  	runConfig *runServerConfig,
  1667  	expectClientBPFField bool,
  1668  	expectServerBPFField bool,
  1669  	expectServerPacketManipulationField bool,
  1670  	expectBurstFields bool,
  1671  	expectTCPPortForwardDial bool,
  1672  	expectTCPDataTransfer bool,
  1673  	expectUDPDataTransfer bool,
  1674  	expectQUICVersion string,
  1675  	expectDestinationBytesFields bool,
  1676  	fields map[string]interface{}) error {
  1677  
  1678  	// Limitations:
  1679  	//
  1680  	// - client_build_rev not set in test build (see common/buildinfo.go)
  1681  	// - egress_region, upstream_proxy_type, upstream_proxy_custom_header_names not exercised in test
  1682  	// - fronting_provider_id/meek_dial_ip_address/meek_resolved_ip_address only logged for FRONTED meek protocols
  1683  
  1684  	for _, name := range []string{
  1685  		"start_time",
  1686  		"duration",
  1687  		"session_id",
  1688  		"is_first_tunnel_in_session",
  1689  		"last_connected",
  1690  		"establishment_duration",
  1691  		"propagation_channel_id",
  1692  		"sponsor_id",
  1693  		"client_platform",
  1694  		"client_features",
  1695  		"relay_protocol",
  1696  		"device_region",
  1697  		"ssh_client_version",
  1698  		"server_entry_region",
  1699  		"server_entry_source",
  1700  		"server_entry_timestamp",
  1701  		"dial_port_number",
  1702  		"is_replay",
  1703  		"dial_duration",
  1704  		"candidate_number",
  1705  		"established_tunnels_count",
  1706  		"network_latency_multiplier",
  1707  		"network_type",
  1708  
  1709  		// The test run ensures that logServerLoad is invoked while the client
  1710  		// is connected, so the following must be logged.
  1711  		"peak_concurrent_proximate_accepted_clients",
  1712  		"peak_concurrent_proximate_established_clients",
  1713  	} {
  1714  		if fields[name] == nil || fmt.Sprintf("%s", fields[name]) == "" {
  1715  			return fmt.Errorf("missing expected field '%s'", name)
  1716  		}
  1717  	}
  1718  
  1719  	if fields["relay_protocol"].(string) != runConfig.tunnelProtocol {
  1720  		return fmt.Errorf("unexpected relay_protocol '%s'", fields["relay_protocol"])
  1721  	}
  1722  
  1723  	if !common.Contains(testSSHClientVersions, fields["ssh_client_version"].(string)) {
  1724  		return fmt.Errorf("unexpected ssh_client_version '%s'", fields["ssh_client_version"])
  1725  	}
  1726  
  1727  	clientFeatures := fields["client_features"].([]interface{})
  1728  	if len(clientFeatures) != len(testClientFeatures) {
  1729  		return fmt.Errorf("unexpected client_features '%s'", fields["client_features"])
  1730  	}
  1731  	for i, feature := range testClientFeatures {
  1732  		if clientFeatures[i].(string) != feature {
  1733  			return fmt.Errorf("unexpected client_features '%s'", fields["client_features"])
  1734  		}
  1735  	}
  1736  
  1737  	if fields["network_type"].(string) != testNetworkType {
  1738  		return fmt.Errorf("unexpected network_type '%s'", fields["network_type"])
  1739  	}
  1740  
  1741  	// With interruptions, timeouts, and retries in some tests, there may be
  1742  	// more than one dangling accepted_client.
  1743  
  1744  	peakConcurrentProximateAcceptedClients :=
  1745  		int(fields["peak_concurrent_proximate_accepted_clients"].(float64))
  1746  	if peakConcurrentProximateAcceptedClients < 0 ||
  1747  		peakConcurrentProximateAcceptedClients > 10 {
  1748  		return fmt.Errorf(
  1749  			"unexpected peak_concurrent_proximate_accepted_clients '%v'",
  1750  			fields["peak_concurrent_proximate_accepted_clients"])
  1751  	}
  1752  
  1753  	peakConcurrentProximateEstablishedClients :=
  1754  		int(fields["peak_concurrent_proximate_established_clients"].(float64))
  1755  	if peakConcurrentProximateEstablishedClients != 0 {
  1756  		return fmt.Errorf(
  1757  			"unexpected peak_concurrent_proximate_established_clients '%v'",
  1758  			fields["peak_concurrent_proximate_established_clients"])
  1759  	}
  1760  
  1761  	// In some negative test cases, no port forwards are attempted, in which
  1762  	// case these fields are not logged.
  1763  
  1764  	if expectTCPDataTransfer {
  1765  
  1766  		if fields["peak_tcp_port_forward_failure_rate"] == nil {
  1767  			return fmt.Errorf("missing expected field 'peak_tcp_port_forward_failure_rate'")
  1768  		}
  1769  		if fields["peak_tcp_port_forward_failure_rate"].(float64) != 0.0 {
  1770  			return fmt.Errorf(
  1771  				"unexpected peak_tcp_port_forward_failure_rate '%v'",
  1772  				fields["peak_tcp_port_forward_failure_rate"])
  1773  		}
  1774  
  1775  		if fields["peak_tcp_port_forward_failure_rate_sample_size"] == nil {
  1776  			return fmt.Errorf("missing expected field 'peak_tcp_port_forward_failure_rate_sample_size'")
  1777  		}
  1778  		if fields["peak_tcp_port_forward_failure_rate_sample_size"].(float64) <= 0.0 {
  1779  			return fmt.Errorf(
  1780  				"unexpected peak_tcp_port_forward_failure_rate_sample_size '%v'",
  1781  				fields["peak_tcp_port_forward_failure_rate_sample_size"])
  1782  		}
  1783  
  1784  	} else {
  1785  
  1786  		if fields["peak_tcp_port_forward_failure_rate"] != nil {
  1787  			return fmt.Errorf("unexpected field 'peak_tcp_port_forward_failure_rate'")
  1788  		}
  1789  
  1790  		if fields["peak_tcp_port_forward_failure_rate_sample_size"] != nil {
  1791  			return fmt.Errorf("unexpected field 'peak_tcp_port_forward_failure_rate_sample_size'")
  1792  		}
  1793  	}
  1794  
  1795  	if expectUDPDataTransfer {
  1796  
  1797  		if fields["peak_dns_failure_rate"] == nil {
  1798  			return fmt.Errorf("missing expected field 'peak_dns_failure_rate'")
  1799  		}
  1800  		if fields["peak_dns_failure_rate"].(float64) != 0.0 {
  1801  			return fmt.Errorf(
  1802  				"unexpected peak_dns_failure_rate '%v'", fields["peak_dns_failure_rate"])
  1803  		}
  1804  
  1805  		if fields["peak_dns_failure_rate_sample_size"] == nil {
  1806  			return fmt.Errorf("missing expected field 'peak_dns_failure_rate_sample_size'")
  1807  		}
  1808  		if fields["peak_dns_failure_rate_sample_size"].(float64) <= 0.0 {
  1809  			return fmt.Errorf(
  1810  				"unexpected peak_dns_failure_rate_sample_size '%v'",
  1811  				fields["peak_dns_failure_rate_sample_size"])
  1812  		}
  1813  
  1814  	} else {
  1815  
  1816  		if fields["peak_dns_failure_rate"] != nil {
  1817  			return fmt.Errorf("unexpected field 'peak_dns_failure_rate'")
  1818  		}
  1819  
  1820  		if fields["peak_dns_failure_rate_sample_size"] != nil {
  1821  			return fmt.Errorf("unexpected field 'peak_dns_failure_rate_sample_size'")
  1822  		}
  1823  	}
  1824  
  1825  	// TODO: the following cases should check that fields are not logged when
  1826  	// not expected.
  1827  
  1828  	if runConfig.doSplitTunnel {
  1829  
  1830  		if fields["split_tunnel"] == nil {
  1831  			return fmt.Errorf("missing expected field 'split_tunnel'")
  1832  		}
  1833  		if fields["split_tunnel"].(bool) != true {
  1834  			return fmt.Errorf("missing split_tunnel value")
  1835  		}
  1836  	}
  1837  
  1838  	if protocol.TunnelProtocolUsesObfuscatedSSH(runConfig.tunnelProtocol) {
  1839  
  1840  		for _, name := range []string{
  1841  			"padding",
  1842  			"pad_response",
  1843  		} {
  1844  			if fields[name] == nil || fmt.Sprintf("%s", fields[name]) == "" {
  1845  				return fmt.Errorf("missing expected field '%s'", name)
  1846  			}
  1847  		}
  1848  	}
  1849  
  1850  	if protocol.TunnelProtocolUsesMeek(runConfig.tunnelProtocol) {
  1851  
  1852  		for _, name := range []string{
  1853  			"user_agent",
  1854  			"meek_transformed_host_name",
  1855  			"meek_cookie_size",
  1856  			"meek_limit_request",
  1857  			"meek_underlying_connection_count",
  1858  			tactics.APPLIED_TACTICS_TAG_PARAMETER_NAME,
  1859  		} {
  1860  			if fields[name] == nil || fmt.Sprintf("%s", fields[name]) == "" {
  1861  				return fmt.Errorf("missing expected field '%s'", name)
  1862  			}
  1863  		}
  1864  
  1865  		if !common.Contains(testUserAgents, fields["user_agent"].(string)) {
  1866  			return fmt.Errorf("unexpected user_agent '%s'", fields["user_agent"])
  1867  		}
  1868  	}
  1869  
  1870  	if protocol.TunnelProtocolUsesMeekHTTP(runConfig.tunnelProtocol) {
  1871  
  1872  		for _, name := range []string{
  1873  			"meek_host_header",
  1874  		} {
  1875  			if fields[name] == nil || fmt.Sprintf("%s", fields[name]) == "" {
  1876  				return fmt.Errorf("missing expected field '%s'", name)
  1877  			}
  1878  		}
  1879  
  1880  		hostName := fields["meek_host_header"].(string)
  1881  		dialPortNumber := int(fields["dial_port_number"].(float64))
  1882  		if dialPortNumber != 80 {
  1883  			hostName, _, _ = net.SplitHostPort(hostName)
  1884  		}
  1885  		if regexp.MustCompile(testCustomHostNameRegex).FindString(hostName) != hostName {
  1886  			return fmt.Errorf("unexpected meek_host_header '%s'", fields["meek_host_header"])
  1887  		}
  1888  
  1889  		for _, name := range []string{
  1890  			"meek_dial_ip_address",
  1891  			"meek_resolved_ip_address",
  1892  		} {
  1893  			if fields[name] != nil {
  1894  				return fmt.Errorf("unexpected field '%s'", name)
  1895  			}
  1896  		}
  1897  	}
  1898  
  1899  	if protocol.TunnelProtocolUsesMeekHTTPS(runConfig.tunnelProtocol) {
  1900  
  1901  		for _, name := range []string{
  1902  			"tls_profile",
  1903  			"tls_version",
  1904  			"meek_sni_server_name",
  1905  		} {
  1906  			if fields[name] == nil || fmt.Sprintf("%s", fields[name]) == "" {
  1907  				return fmt.Errorf("missing expected field '%s'", name)
  1908  			}
  1909  		}
  1910  
  1911  		hostName := fields["meek_sni_server_name"].(string)
  1912  		if regexp.MustCompile(testCustomHostNameRegex).FindString(hostName) != hostName {
  1913  			return fmt.Errorf("unexpected meek_sni_server_name '%s'", fields["meek_sni_server_name"])
  1914  		}
  1915  
  1916  		for _, name := range []string{
  1917  			"meek_dial_ip_address",
  1918  			"meek_resolved_ip_address",
  1919  			"meek_host_header",
  1920  		} {
  1921  			if fields[name] != nil {
  1922  				return fmt.Errorf("unexpected field '%s'", name)
  1923  			}
  1924  		}
  1925  
  1926  		if !common.Contains(protocol.SupportedTLSProfiles, fields["tls_profile"].(string)) {
  1927  			return fmt.Errorf("unexpected tls_profile '%s'", fields["tls_profile"])
  1928  		}
  1929  
  1930  		tlsVersion := fields["tls_version"].(string)
  1931  		if !strings.HasPrefix(tlsVersion, protocol.TLS_VERSION_12) &&
  1932  			!strings.HasPrefix(tlsVersion, protocol.TLS_VERSION_13) {
  1933  			return fmt.Errorf("unexpected tls_version '%s'", fields["tls_version"])
  1934  		}
  1935  	}
  1936  
  1937  	if protocol.TunnelProtocolUsesQUIC(runConfig.tunnelProtocol) {
  1938  
  1939  		for _, name := range []string{
  1940  			"quic_version",
  1941  			"quic_dial_sni_address",
  1942  		} {
  1943  			if fields[name] == nil || fmt.Sprintf("%s", fields[name]) == "" {
  1944  				return fmt.Errorf("missing expected field '%s'", name)
  1945  			}
  1946  		}
  1947  
  1948  		quicVersion := fields["quic_version"].(string)
  1949  		if !common.Contains(protocol.SupportedQUICVersions, quicVersion) ||
  1950  			(runConfig.limitQUICVersions && quicVersion != expectQUICVersion) {
  1951  
  1952  			return fmt.Errorf("unexpected quic_version '%s'", fields["quic_version"])
  1953  		}
  1954  	}
  1955  
  1956  	if runConfig.forceFragmenting {
  1957  
  1958  		for _, name := range []string{
  1959  			"upstream_bytes_fragmented",
  1960  			"upstream_min_bytes_written",
  1961  			"upstream_max_bytes_written",
  1962  			"upstream_min_delayed",
  1963  			"upstream_max_delayed",
  1964  		} {
  1965  			if fields[name] == nil || fmt.Sprintf("%s", fields[name]) == "" {
  1966  				return fmt.Errorf("missing expected field '%s'", name)
  1967  			}
  1968  		}
  1969  	}
  1970  
  1971  	if expectClientBPFField {
  1972  		name := "client_bpf"
  1973  		if fields[name] == nil {
  1974  			return fmt.Errorf("missing expected field '%s'", name)
  1975  		} else if fmt.Sprintf("%s", fields[name]) != "test-client-bpf" {
  1976  			return fmt.Errorf("unexpected field value %s: '%s'", name, fields[name])
  1977  		}
  1978  	}
  1979  
  1980  	if expectServerBPFField {
  1981  		name := "server_bpf"
  1982  		if fields[name] == nil {
  1983  			return fmt.Errorf("missing expected field '%s'", name)
  1984  		} else if fmt.Sprintf("%s", fields[name]) != "test-server-bpf" {
  1985  			return fmt.Errorf("unexpected field value %s: '%s'", name, fields[name])
  1986  		}
  1987  	}
  1988  
  1989  	if expectServerPacketManipulationField {
  1990  		name := "server_packet_manipulation"
  1991  		if fields[name] == nil {
  1992  			return fmt.Errorf("missing expected field '%s'", name)
  1993  		} else if fmt.Sprintf("%s", fields[name]) != "test-packetman-spec" {
  1994  			return fmt.Errorf("unexpected field value %s: '%s'", name, fields[name])
  1995  		}
  1996  	}
  1997  
  1998  	if expectBurstFields {
  1999  
  2000  		// common.TestBurstMonitoredConn covers inclusion of additional fields.
  2001  		for _, name := range []string{
  2002  			"burst_upstream_first_rate",
  2003  			"burst_upstream_last_rate",
  2004  			"burst_upstream_min_rate",
  2005  			"burst_upstream_max_rate",
  2006  			"burst_downstream_first_rate",
  2007  			"burst_downstream_last_rate",
  2008  			"burst_downstream_min_rate",
  2009  			"burst_downstream_max_rate",
  2010  		} {
  2011  			if fields[name] == nil || fmt.Sprintf("%s", fields[name]) == "" {
  2012  				return fmt.Errorf("missing expected field '%s'", name)
  2013  			}
  2014  		}
  2015  	}
  2016  
  2017  	var checkTCPMetric func(float64) bool
  2018  	if expectTCPPortForwardDial {
  2019  		checkTCPMetric = func(f float64) bool { return f > 0 }
  2020  	} else {
  2021  		checkTCPMetric = func(f float64) bool { return f == 0 }
  2022  	}
  2023  
  2024  	for _, name := range []string{
  2025  		"peak_concurrent_dialing_port_forward_count_tcp",
  2026  	} {
  2027  		if fields[name] == nil {
  2028  			return fmt.Errorf("missing expected field '%s'", name)
  2029  		}
  2030  		if !checkTCPMetric(fields[name].(float64)) {
  2031  			return fmt.Errorf("unexpected field value %s: '%v'", name, fields[name])
  2032  		}
  2033  	}
  2034  
  2035  	if expectTCPDataTransfer {
  2036  		checkTCPMetric = func(f float64) bool { return f > 0 }
  2037  	} else {
  2038  		checkTCPMetric = func(f float64) bool { return f == 0 }
  2039  	}
  2040  
  2041  	for _, name := range []string{
  2042  		"bytes_up_tcp",
  2043  		"bytes_down_tcp",
  2044  		"peak_concurrent_port_forward_count_tcp",
  2045  		"total_port_forward_count_tcp",
  2046  	} {
  2047  		if fields[name] == nil {
  2048  			return fmt.Errorf("missing expected field '%s'", name)
  2049  		}
  2050  		if !checkTCPMetric(fields[name].(float64)) {
  2051  			return fmt.Errorf("unexpected field value %s: '%v'", name, fields[name])
  2052  		}
  2053  	}
  2054  
  2055  	var checkUDPMetric func(float64) bool
  2056  	if expectUDPDataTransfer {
  2057  		checkUDPMetric = func(f float64) bool { return f > 0 }
  2058  	} else {
  2059  		checkUDPMetric = func(f float64) bool { return f == 0 }
  2060  	}
  2061  
  2062  	for _, name := range []string{
  2063  		"bytes_up_udp",
  2064  		"bytes_down_udp",
  2065  		"peak_concurrent_port_forward_count_udp",
  2066  		"total_port_forward_count_udp",
  2067  		"total_udpgw_channel_count",
  2068  	} {
  2069  		if fields[name] == nil {
  2070  			return fmt.Errorf("missing expected field '%s'", name)
  2071  		}
  2072  		if !checkUDPMetric(fields[name].(float64)) {
  2073  			return fmt.Errorf("unexpected field value %s: '%v'", name, fields[name])
  2074  		}
  2075  	}
  2076  
  2077  	for _, name := range []string{
  2078  		"dest_bytes_asn",
  2079  		"dest_bytes_up_tcp",
  2080  		"dest_bytes_down_tcp",
  2081  		"dest_bytes_up_udp",
  2082  		"dest_bytes_down_udp",
  2083  		"dest_bytes",
  2084  	} {
  2085  		if expectDestinationBytesFields && fields[name] == nil {
  2086  			return fmt.Errorf("missing expected field '%s'", name)
  2087  
  2088  		} else if !expectDestinationBytesFields && fields[name] != nil {
  2089  			return fmt.Errorf("unexpected field '%s'", name)
  2090  		}
  2091  	}
  2092  
  2093  	if expectDestinationBytesFields {
  2094  		name := "dest_bytes_asn"
  2095  		if fields[name].(string) != testGeoIPASN {
  2096  			return fmt.Errorf("unexpected field value %s: '%v'", name, fields[name])
  2097  		}
  2098  		for _, pair := range [][]string{
  2099  			[]string{"dest_bytes_up_tcp", "bytes_up_tcp"},
  2100  			[]string{"dest_bytes_down_tcp", "bytes_down_tcp"},
  2101  			[]string{"dest_bytes_up_udp", "bytes_up_udp"},
  2102  			[]string{"dest_bytes_down_udp", "bytes_down_udp"},
  2103  			[]string{"dest_bytes", "bytes"},
  2104  		} {
  2105  			value0 := int64(fields[pair[0]].(float64))
  2106  			value1 := int64(fields[pair[1]].(float64))
  2107  			ok := value0 == value1
  2108  			if pair[0] == "dest_bytes_up_udp" || pair[0] == "dest_bytes_down_udp" || pair[0] == "dest_bytes" {
  2109  				// DNS requests are excluded from destination bytes counting
  2110  				ok = value0 > 0 && value0 < value1
  2111  			}
  2112  			if !ok {
  2113  				return fmt.Errorf("unexpected field value %s: %v != %v", pair[0], fields[pair[0]], fields[pair[1]])
  2114  			}
  2115  		}
  2116  	}
  2117  
  2118  	return nil
  2119  }
  2120  
  2121  func checkExpectedUniqueUserLogFields(
  2122  	runConfig *runServerConfig,
  2123  	fields map[string]interface{}) error {
  2124  
  2125  	for _, name := range []string{
  2126  		"session_id",
  2127  		"last_connected",
  2128  		"propagation_channel_id",
  2129  		"sponsor_id",
  2130  		"client_platform",
  2131  		"device_region",
  2132  	} {
  2133  		if fields[name] == nil || fmt.Sprintf("%s", fields[name]) == "" {
  2134  			return fmt.Errorf("missing expected field '%s'", name)
  2135  		}
  2136  	}
  2137  
  2138  	return nil
  2139  }
  2140  
  2141  func checkExpectedDomainBytesLogFields(
  2142  	runConfig *runServerConfig,
  2143  	fields map[string]interface{}) error {
  2144  
  2145  	for _, name := range []string{
  2146  		"session_id",
  2147  		"propagation_channel_id",
  2148  		"sponsor_id",
  2149  		"client_platform",
  2150  		"device_region",
  2151  		"domain",
  2152  		"bytes",
  2153  	} {
  2154  		if fields[name] == nil || fmt.Sprintf("%s", fields[name]) == "" {
  2155  			return fmt.Errorf("missing expected field '%s'", name)
  2156  		}
  2157  
  2158  		if name == "domain" {
  2159  			if fields[name].(string) != "ALL" && fields[name].(string) != "(OTHER)" {
  2160  				return fmt.Errorf("unexpected field value %s: '%v'", name, fields[name])
  2161  			}
  2162  		}
  2163  	}
  2164  
  2165  	return nil
  2166  }
  2167  
  2168  func makeTunneledWebRequest(
  2169  	t *testing.T,
  2170  	localHTTPProxyPort int,
  2171  	requestURL, expectedResponseBody string) error {
  2172  
  2173  	roundTripTimeout := 30 * time.Second
  2174  
  2175  	proxyUrl, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", localHTTPProxyPort))
  2176  	if err != nil {
  2177  		return fmt.Errorf("error initializing proxied HTTP request: %s", err)
  2178  	}
  2179  
  2180  	httpClient := &http.Client{
  2181  		Transport: &http.Transport{
  2182  			Proxy: http.ProxyURL(proxyUrl),
  2183  		},
  2184  		Timeout: roundTripTimeout,
  2185  	}
  2186  
  2187  	response, err := httpClient.Get(requestURL)
  2188  	if err != nil {
  2189  		return fmt.Errorf("error sending proxied HTTP request: %s", err)
  2190  	}
  2191  
  2192  	body, err := ioutil.ReadAll(response.Body)
  2193  	if err != nil {
  2194  		return fmt.Errorf("error reading proxied HTTP response: %s", err)
  2195  	}
  2196  	response.Body.Close()
  2197  
  2198  	if string(body) != expectedResponseBody {
  2199  		return fmt.Errorf("unexpected proxied HTTP response")
  2200  	}
  2201  
  2202  	return nil
  2203  }
  2204  
  2205  func makeTunneledNTPRequest(t *testing.T, localSOCKSProxyPort int, udpgwServerAddress string) error {
  2206  
  2207  	timeout := 20 * time.Second
  2208  	var err error
  2209  
  2210  	testHostnames := []string{"time.google.com", "time.nist.gov", "pool.ntp.org"}
  2211  	indexes := prng.Perm(len(testHostnames))
  2212  
  2213  	for _, index := range indexes {
  2214  		testHostname := testHostnames[index]
  2215  		err = makeTunneledNTPRequestAttempt(t, testHostname, timeout, localSOCKSProxyPort, udpgwServerAddress)
  2216  		if err == nil {
  2217  			break
  2218  		}
  2219  		t.Logf("makeTunneledNTPRequestAttempt failed: %s", err)
  2220  	}
  2221  
  2222  	return err
  2223  }
  2224  
  2225  var nextUDPProxyPort = 7300
  2226  
  2227  func makeTunneledNTPRequestAttempt(
  2228  	t *testing.T, testHostname string, timeout time.Duration, localSOCKSProxyPort int, udpgwServerAddress string) error {
  2229  
  2230  	nextUDPProxyPort++
  2231  	localUDPProxyAddress, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", nextUDPProxyPort))
  2232  	if err != nil {
  2233  		return fmt.Errorf("ResolveUDPAddr failed: %s", err)
  2234  	}
  2235  
  2236  	// Note: this proxy is intended for this test only -- it only accepts a single connection,
  2237  	// handles it, and then terminates.
  2238  
  2239  	localUDPProxy := func(destinationIP net.IP, destinationPort uint16, waitGroup *sync.WaitGroup) {
  2240  
  2241  		if waitGroup != nil {
  2242  			defer waitGroup.Done()
  2243  		}
  2244  
  2245  		destination := net.JoinHostPort(destinationIP.String(), strconv.Itoa(int(destinationPort)))
  2246  
  2247  		serverUDPConn, err := net.ListenUDP("udp", localUDPProxyAddress)
  2248  		if err != nil {
  2249  			t.Logf("ListenUDP for %s failed: %s", destination, err)
  2250  			return
  2251  		}
  2252  		defer serverUDPConn.Close()
  2253  
  2254  		udpgwPreambleSize := 11 // see writeUdpgwPreamble
  2255  		buffer := make([]byte, udpgwProtocolMaxMessageSize)
  2256  		packetSize, clientAddr, err := serverUDPConn.ReadFromUDP(
  2257  			buffer[udpgwPreambleSize:])
  2258  		if err != nil {
  2259  			t.Logf("serverUDPConn.Read for %s failed: %s", destination, err)
  2260  			return
  2261  		}
  2262  
  2263  		socksProxyAddress := fmt.Sprintf("127.0.0.1:%d", localSOCKSProxyPort)
  2264  
  2265  		dialer, err := proxy.SOCKS5("tcp", socksProxyAddress, nil, proxy.Direct)
  2266  		if err != nil {
  2267  			t.Logf("proxy.SOCKS5 for %s failed: %s", destination, err)
  2268  			return
  2269  		}
  2270  
  2271  		socksTCPConn, err := dialer.Dial("tcp", udpgwServerAddress)
  2272  		if err != nil {
  2273  			t.Logf("dialer.Dial for %s failed: %s", destination, err)
  2274  			return
  2275  		}
  2276  		defer socksTCPConn.Close()
  2277  
  2278  		flags := uint8(0)
  2279  		if destinationPort == 53 {
  2280  			flags = udpgwProtocolFlagDNS
  2281  		}
  2282  
  2283  		err = writeUdpgwPreamble(
  2284  			udpgwPreambleSize,
  2285  			flags,
  2286  			0,
  2287  			destinationIP,
  2288  			destinationPort,
  2289  			uint16(packetSize),
  2290  			buffer)
  2291  		if err != nil {
  2292  			t.Logf("writeUdpgwPreamble for %s failed: %s", destination, err)
  2293  			return
  2294  		}
  2295  
  2296  		_, err = socksTCPConn.Write(buffer[0 : udpgwPreambleSize+packetSize])
  2297  		if err != nil {
  2298  			t.Logf("socksTCPConn.Write for %s failed: %s", destination, err)
  2299  			return
  2300  		}
  2301  
  2302  		udpgwProtocolMessage, err := readUdpgwMessage(socksTCPConn, buffer)
  2303  		if err != nil {
  2304  			t.Logf("readUdpgwMessage for %s failed: %s", destination, err)
  2305  			return
  2306  		}
  2307  
  2308  		_, err = serverUDPConn.WriteToUDP(udpgwProtocolMessage.packet, clientAddr)
  2309  		if err != nil {
  2310  			t.Logf("serverUDPConn.Write for %s failed: %s", destination, err)
  2311  			return
  2312  		}
  2313  	}
  2314  
  2315  	// Tunneled DNS request
  2316  
  2317  	waitGroup := new(sync.WaitGroup)
  2318  	waitGroup.Add(1)
  2319  	go localUDPProxy(
  2320  		net.IP(make([]byte, 4)), // ignored due to transparent DNS forwarding
  2321  		53,
  2322  		waitGroup)
  2323  	// TODO: properly synchronize with local UDP proxy startup
  2324  	time.Sleep(1 * time.Second)
  2325  
  2326  	clientUDPConn, err := net.DialUDP("udp", nil, localUDPProxyAddress)
  2327  	if err != nil {
  2328  		return fmt.Errorf("DialUDP failed: %s", err)
  2329  	}
  2330  
  2331  	clientUDPConn.SetReadDeadline(time.Now().Add(timeout))
  2332  	clientUDPConn.SetWriteDeadline(time.Now().Add(timeout))
  2333  
  2334  	addrs, err := resolveIP(testHostname, clientUDPConn)
  2335  
  2336  	clientUDPConn.Close()
  2337  
  2338  	if err == nil && (len(addrs) == 0 || len(addrs[0]) < 4) {
  2339  		err = std_errors.New("no address")
  2340  	}
  2341  	if err != nil {
  2342  		return fmt.Errorf("resolveIP failed: %s", err)
  2343  	}
  2344  
  2345  	waitGroup.Wait()
  2346  
  2347  	// Tunneled NTP request
  2348  
  2349  	waitGroup = new(sync.WaitGroup)
  2350  	waitGroup.Add(1)
  2351  	go localUDPProxy(
  2352  		addrs[0][len(addrs[0])-4:],
  2353  		123,
  2354  		waitGroup)
  2355  	// TODO: properly synchronize with local UDP proxy startup
  2356  	time.Sleep(1 * time.Second)
  2357  
  2358  	clientUDPConn, err = net.DialUDP("udp", nil, localUDPProxyAddress)
  2359  	if err != nil {
  2360  		return fmt.Errorf("DialUDP failed: %s", err)
  2361  	}
  2362  
  2363  	clientUDPConn.SetReadDeadline(time.Now().Add(timeout))
  2364  	clientUDPConn.SetWriteDeadline(time.Now().Add(timeout))
  2365  
  2366  	// NTP protocol code from: https://groups.google.com/d/msg/golang-nuts/FlcdMU5fkLQ/CAeoD9eqm-IJ
  2367  
  2368  	ntpData := make([]byte, 48)
  2369  	ntpData[0] = 3<<3 | 3
  2370  
  2371  	_, err = clientUDPConn.Write(ntpData)
  2372  	if err != nil {
  2373  		clientUDPConn.Close()
  2374  		return fmt.Errorf("NTP Write failed: %s", err)
  2375  	}
  2376  
  2377  	_, err = clientUDPConn.Read(ntpData)
  2378  	if err != nil {
  2379  		clientUDPConn.Close()
  2380  		return fmt.Errorf("NTP Read failed: %s", err)
  2381  	}
  2382  
  2383  	clientUDPConn.Close()
  2384  
  2385  	var sec, frac uint64
  2386  	sec = uint64(ntpData[43]) | uint64(ntpData[42])<<8 | uint64(ntpData[41])<<16 | uint64(ntpData[40])<<24
  2387  	frac = uint64(ntpData[47]) | uint64(ntpData[46])<<8 | uint64(ntpData[45])<<16 | uint64(ntpData[44])<<24
  2388  
  2389  	nsec := sec * 1e9
  2390  	nsec += (frac * 1e9) >> 32
  2391  
  2392  	ntpNow := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nsec)).Local()
  2393  
  2394  	now := time.Now()
  2395  
  2396  	diff := ntpNow.Sub(now)
  2397  	if diff < 0 {
  2398  		diff = -diff
  2399  	}
  2400  
  2401  	if diff > 1*time.Minute {
  2402  		return fmt.Errorf("Unexpected NTP time: %s; local time: %s", ntpNow, now)
  2403  	}
  2404  
  2405  	waitGroup.Wait()
  2406  
  2407  	return nil
  2408  }
  2409  
  2410  func resolveIP(host string, conn net.Conn) (addrs []net.IP, err error) {
  2411  
  2412  	// Send the DNS query (A record only)
  2413  	dnsConn := &dns.Conn{Conn: conn}
  2414  	defer dnsConn.Close()
  2415  	query := new(dns.Msg)
  2416  	query.SetQuestion(dns.Fqdn(host), dns.TypeA)
  2417  	query.RecursionDesired = true
  2418  	dnsConn.WriteMsg(query)
  2419  
  2420  	// Process the response
  2421  	response, err := dnsConn.ReadMsg()
  2422  	if err == nil && response.MsgHdr.Id != query.MsgHdr.Id {
  2423  		err = dns.ErrId
  2424  	}
  2425  	if err != nil {
  2426  		return nil, errors.Trace(err)
  2427  	}
  2428  	addrs = make([]net.IP, 0)
  2429  	for _, answer := range response.Answer {
  2430  		if a, ok := answer.(*dns.A); ok {
  2431  			addrs = append(addrs, a.A)
  2432  		}
  2433  	}
  2434  	return addrs, nil
  2435  }
  2436  
  2437  func pavePsinetDatabaseFile(
  2438  	t *testing.T,
  2439  	psinetFilename string,
  2440  	sponsorID string,
  2441  	useDefaultSponsorID bool,
  2442  	doDomainBytes bool,
  2443  	validServerEntryTags []string) (string, string) {
  2444  
  2445  	if sponsorID == "" {
  2446  		sponsorID = prng.HexString(8)
  2447  	}
  2448  
  2449  	defaultSponsorID := ""
  2450  	if useDefaultSponsorID {
  2451  		defaultSponsorID = sponsorID
  2452  	}
  2453  
  2454  	fakeDomain := prng.HexString(4)
  2455  	fakePath := prng.HexString(4)
  2456  	expectedHomepageURL := fmt.Sprintf("https://%s.com/%s", fakeDomain, fakePath)
  2457  
  2458  	psinetJSONFormat := `
  2459      {
  2460          "default_sponsor_id" : "%s",
  2461          "sponsors" : {
  2462              "%s" : {
  2463                  %s
  2464                  "home_pages" : {
  2465                      "None" : [
  2466                          {
  2467                              "region" : null,
  2468                              "url" : "%s"
  2469                          }
  2470                      ]
  2471                  }
  2472              }
  2473          },
  2474          "default_alert_action_urls" : {
  2475              "%s" : %s
  2476          },
  2477          "valid_server_entry_tags" : {
  2478              %s
  2479          }
  2480      }
  2481  	`
  2482  
  2483  	domainBytes := ""
  2484  	if doDomainBytes {
  2485  		domainBytes = `
  2486                  "https_request_regexes" : [
  2487                      {
  2488                          "regex" : ".*",
  2489                          "replace" : "ALL"
  2490                      }
  2491                  ],
  2492  	`
  2493  	}
  2494  
  2495  	actionURLsJSON, _ := json.Marshal(testDisallowedTrafficAlertActionURLs)
  2496  
  2497  	validServerEntryTagsJSON := ""
  2498  	for _, serverEntryTag := range validServerEntryTags {
  2499  		if len(validServerEntryTagsJSON) > 0 {
  2500  			validServerEntryTagsJSON += ", "
  2501  		}
  2502  		validServerEntryTagsJSON += fmt.Sprintf("\"%s\" : true", serverEntryTag)
  2503  	}
  2504  
  2505  	psinetJSON := fmt.Sprintf(
  2506  		psinetJSONFormat,
  2507  		defaultSponsorID,
  2508  		sponsorID,
  2509  		domainBytes,
  2510  		expectedHomepageURL,
  2511  		protocol.PSIPHON_API_ALERT_DISALLOWED_TRAFFIC,
  2512  		actionURLsJSON,
  2513  		validServerEntryTagsJSON)
  2514  
  2515  	err := ioutil.WriteFile(psinetFilename, []byte(psinetJSON), 0600)
  2516  	if err != nil {
  2517  		t.Fatalf("error paving psinet database file: %s", err)
  2518  	}
  2519  
  2520  	return sponsorID, expectedHomepageURL
  2521  }
  2522  
  2523  func paveTrafficRulesFile(
  2524  	t *testing.T,
  2525  	trafficRulesFilename string,
  2526  	propagationChannelID string,
  2527  	accessType string,
  2528  	authorizationID string,
  2529  	requireAuthorization bool,
  2530  	deny bool,
  2531  	livenessTestSize int) {
  2532  
  2533  	// Test both default and fast lookups
  2534  	if intLookupThreshold != 10 {
  2535  		t.Fatalf("unexpected intLookupThreshold")
  2536  	}
  2537  
  2538  	TCPPorts := mockWebServerPort
  2539  	UDPPorts := "53, 123, 10001, 10002, 10003, 10004, 10005, 10006, 10007, 10008, 10009, 10010"
  2540  
  2541  	allowTCPPorts := TCPPorts
  2542  	allowUDPPorts := UDPPorts
  2543  	disallowTCPPorts := "1"
  2544  	disallowUDPPorts := "1"
  2545  
  2546  	if deny {
  2547  		allowTCPPorts = "1"
  2548  		allowUDPPorts = "1"
  2549  		disallowTCPPorts = TCPPorts
  2550  		disallowUDPPorts = UDPPorts
  2551  	}
  2552  
  2553  	authorizationFilterFormat := `,
  2554                      "AuthorizedAccessTypes" : ["%s"],
  2555                      "ActiveAuthorizationIDs" : ["%s"]
  2556  	`
  2557  
  2558  	authorizationFilter := ""
  2559  	if requireAuthorization {
  2560  		authorizationFilter = fmt.Sprintf(
  2561  			authorizationFilterFormat, accessType, authorizationID)
  2562  	}
  2563  
  2564  	// Supports two traffic rule test cases:
  2565  	//
  2566  	// 1. no ports are allowed until after the filtered rule is applied
  2567  	// 2. no required ports are allowed (deny = true)
  2568  
  2569  	trafficRulesJSONFormat := `
  2570      {
  2571          "DefaultRules" :  {
  2572              "RateLimits" : {
  2573                  "ReadBytesPerSecond": 16384,
  2574                  "WriteBytesPerSecond": 16384,
  2575                  "ReadUnthrottledBytes": %d,
  2576                  "WriteUnthrottledBytes": %d
  2577              },
  2578              "AllowTCPPorts" : [1],
  2579              "AllowUDPPorts" : [1],
  2580              "MeekRateLimiterHistorySize" : 10,
  2581              "MeekRateLimiterThresholdSeconds" : 1,
  2582              "MeekRateLimiterGarbageCollectionTriggerCount" : 1,
  2583              "MeekRateLimiterReapHistoryFrequencySeconds" : 1,
  2584              "MeekRateLimiterRegions" : []
  2585          },
  2586          "FilteredRules" : [
  2587              {
  2588                  "Filter" : {
  2589                      "HandshakeParameters" : {
  2590                          "propagation_channel_id" : ["%s"]
  2591                      }%s
  2592                  },
  2593                  "Rules" : {
  2594                      "RateLimits" : {
  2595                          "ReadBytesPerSecond": 2097152,
  2596                          "WriteBytesPerSecond": 2097152
  2597                      },
  2598                      "AllowTCPPorts" : [%s],
  2599                      "AllowUDPPorts" : [%s],
  2600                      "DisallowTCPPorts" : [%s],
  2601                      "DisallowUDPPorts" : [%s]
  2602                  }
  2603              }
  2604          ]
  2605      }
  2606      `
  2607  
  2608  	trafficRulesJSON := fmt.Sprintf(
  2609  		trafficRulesJSONFormat,
  2610  		livenessTestSize, livenessTestSize,
  2611  		propagationChannelID, authorizationFilter,
  2612  		allowTCPPorts, allowUDPPorts, disallowTCPPorts, disallowUDPPorts)
  2613  
  2614  	err := ioutil.WriteFile(trafficRulesFilename, []byte(trafficRulesJSON), 0600)
  2615  	if err != nil {
  2616  		t.Fatalf("error paving traffic rules file: %s", err)
  2617  	}
  2618  }
  2619  
  2620  var expectedNumSLOKs = 3
  2621  
  2622  func paveOSLConfigFile(t *testing.T, oslConfigFilename string) string {
  2623  
  2624  	oslConfigJSONFormat := `
  2625      {
  2626        "Schemes" : [
  2627          {
  2628            "Epoch" : "%s",
  2629            "Regions" : [],
  2630            "PropagationChannelIDs" : ["%s"],
  2631            "MasterKey" : "wFuSbqU/pJ/35vRmoM8T9ys1PgDa8uzJps1Y+FNKa5U=",
  2632            "SeedSpecs" : [
  2633              {
  2634                "ID" : "IXHWfVgWFkEKvgqsjmnJuN3FpaGuCzQMETya+DSQvsk=",
  2635                "UpstreamSubnets" : ["0.0.0.0/0"],
  2636                "Targets" :
  2637                {
  2638                    "BytesRead" : 1,
  2639                    "BytesWritten" : 1,
  2640                    "PortForwardDurationNanoseconds" : 1
  2641                }
  2642              },
  2643              {
  2644                "ID" : "qvpIcORLE2Pi5TZmqRtVkEp+OKov0MhfsYPLNV7FYtI=",
  2645                "UpstreamSubnets" : ["0.0.0.0/0"],
  2646                "Targets" :
  2647                {
  2648                    "BytesRead" : 1,
  2649                    "BytesWritten" : 1,
  2650                    "PortForwardDurationNanoseconds" : 1
  2651                }
  2652              }
  2653            ],
  2654            "SeedSpecThreshold" : 2,
  2655            "SeedPeriodNanoseconds" : 2592000000000000,
  2656            "SeedPeriodKeySplits": [
  2657              {
  2658                "Total": 2,
  2659                "Threshold": 2
  2660              }
  2661            ]
  2662          },
  2663          {
  2664            "Epoch" : "%s",
  2665            "Regions" : [],
  2666            "PropagationChannelIDs" : ["%s"],
  2667            "MasterKey" : "HDc/mvd7e+lKDJD0fMpJW66YJ/VW4iqDRjeclEsMnro=",
  2668            "SeedSpecs" : [
  2669              {
  2670                "ID" : "/M0vsT0IjzmI0MvTI9IYe8OVyeQGeaPZN2xGxfLw/UQ=",
  2671                "UpstreamSubnets" : ["0.0.0.0/0"],
  2672                "Targets" :
  2673                {
  2674                    "BytesRead" : 1,
  2675                    "BytesWritten" : 1,
  2676                    "PortForwardDurationNanoseconds" : 1
  2677                }
  2678              }
  2679            ],
  2680            "SeedSpecThreshold" : 1,
  2681            "SeedPeriodNanoseconds" : 2592000000000000,
  2682            "SeedPeriodKeySplits": [
  2683              {
  2684                "Total": 1,
  2685                "Threshold": 1
  2686              }
  2687            ]
  2688          }
  2689        ]
  2690      }
  2691      `
  2692  
  2693  	propagationChannelID := prng.HexString(8)
  2694  
  2695  	now := time.Now().UTC()
  2696  	epoch := now.Truncate(720 * time.Hour)
  2697  	epochStr := epoch.Format(time.RFC3339Nano)
  2698  
  2699  	oslConfigJSON := fmt.Sprintf(
  2700  		oslConfigJSONFormat,
  2701  		epochStr, propagationChannelID,
  2702  		epochStr, propagationChannelID)
  2703  
  2704  	err := ioutil.WriteFile(oslConfigFilename, []byte(oslConfigJSON), 0600)
  2705  	if err != nil {
  2706  		t.Fatalf("error paving osl config file: %s", err)
  2707  	}
  2708  
  2709  	return propagationChannelID
  2710  }
  2711  
  2712  func paveTacticsConfigFile(
  2713  	t *testing.T, tacticsConfigFilename string,
  2714  	tacticsRequestPublicKey, tacticsRequestPrivateKey, tacticsRequestObfuscatedKey string,
  2715  	tunnelProtocol string,
  2716  	propagationChannelID string,
  2717  	livenessTestSize int,
  2718  	doBurstMonitor bool,
  2719  	doDestinationBytes bool) {
  2720  
  2721  	// Setting LimitTunnelProtocols passively exercises the
  2722  	// server-side LimitTunnelProtocols enforcement.
  2723  
  2724  	tacticsConfigJSONFormat := `
  2725      {
  2726        "RequestPublicKey" : "%s",
  2727        "RequestPrivateKey" : "%s",
  2728        "RequestObfuscatedKey" : "%s",
  2729        "DefaultTactics" : {
  2730          "TTL" : "60s",
  2731          "Probability" : 1.0,
  2732          "Parameters" : {
  2733            %s
  2734            %s
  2735            "LimitTunnelProtocols" : ["%s"],
  2736            "FragmentorLimitProtocols" : ["%s"],
  2737            "FragmentorProbability" : 1.0,
  2738            "FragmentorMinTotalBytes" : 1000,
  2739            "FragmentorMaxTotalBytes" : 2000,
  2740            "FragmentorMinWriteBytes" : 1,
  2741            "FragmentorMaxWriteBytes" : 100,
  2742            "FragmentorMinDelay" : "1ms",
  2743            "FragmentorMaxDelay" : "10ms",
  2744            "FragmentorDownstreamLimitProtocols" : ["%s"],
  2745            "FragmentorDownstreamProbability" : 1.0,
  2746            "FragmentorDownstreamMinTotalBytes" : 1000,
  2747            "FragmentorDownstreamMaxTotalBytes" : 2000,
  2748            "FragmentorDownstreamMinWriteBytes" : 1,
  2749            "FragmentorDownstreamMaxWriteBytes" : 100,
  2750            "FragmentorDownstreamMinDelay" : "1ms",
  2751            "FragmentorDownstreamMaxDelay" : "10ms",
  2752            "LivenessTestMinUpstreamBytes" : %d,
  2753            "LivenessTestMaxUpstreamBytes" : %d,
  2754            "LivenessTestMinDownstreamBytes" : %d,
  2755            "LivenessTestMaxDownstreamBytes" : %d,
  2756            "BPFServerTCPProgram": {
  2757              "Name" : "test-server-bpf",
  2758                "Instructions" : [
  2759                  {"Op": "RetConstant", "Args": {"Val": 65535}}]},
  2760            "BPFServerTCPProbability" : 1.0,
  2761            "BPFClientTCPProgram": {
  2762              "Name" : "test-client-bpf",
  2763                "Instructions" : [
  2764                  {"Op": "RetConstant", "Args": {"Val": 65535}}]},
  2765            "BPFClientTCPProbability" : 1.0,
  2766            "ServerPacketManipulationSpecs" : [{"Name": "test-packetman-spec", "PacketSpecs": [["TCP-flags S"]]}],
  2767            "ServerPacketManipulationProbability" : 1.0,
  2768            "ServerProtocolPacketManipulations": {"All" : ["test-packetman-spec"]}
  2769          }
  2770        },
  2771        "FilteredTactics" : [
  2772          {
  2773            "Filter" : {
  2774              "APIParameters" : {"propagation_channel_id" : ["%s"]},
  2775              "SpeedTestRTTMilliseconds" : {
  2776                "Aggregation" : "Median",
  2777                "AtLeast" : 1
  2778              }
  2779            },
  2780            "Tactics" : {
  2781              "Parameters" : {
  2782                "TunnelConnectTimeout" : "20s",
  2783                "TunnelRateLimits" : {"WriteBytesPerSecond": 1000000},
  2784                "TransformHostNameProbability" : 1.0,
  2785                "PickUserAgentProbability" : 1.0,
  2786                "ApplicationParameters" : {
  2787                  "AppFlag1" : true,
  2788                  "AppConfig1" : {"Option1" : "A", "Option2" : "B"},
  2789                  "AppSwitches1" : [1, 2, 3, 4]
  2790                },
  2791                "CustomHostNameRegexes": ["%s"],
  2792                "CustomHostNameProbability": 1.0,
  2793                "CustomHostNameLimitProtocols": ["%s"]
  2794              }
  2795            }
  2796          }
  2797        ]
  2798      }
  2799      `
  2800  
  2801  	burstParameters := ""
  2802  	if doBurstMonitor {
  2803  		burstParameters = `
  2804            "ServerBurstUpstreamDeadline" : "100ms",
  2805            "ServerBurstUpstreamTargetBytes" : 1000,
  2806            "ServerBurstDownstreamDeadline" : "100ms",
  2807            "ServerBurstDownstreamTargetBytes" : 100000,
  2808            "ClientBurstUpstreamDeadline" : "100ms",
  2809            "ClientBurstUpstreamTargetBytes" : 1000,
  2810            "ClientBurstDownstreamDeadline" : "100ms",
  2811            "ClientBurstDownstreamTargetBytes" : 100000,
  2812  	`
  2813  	}
  2814  
  2815  	destinationBytesParameters := ""
  2816  	if doDestinationBytes {
  2817  		destinationBytesParameters = fmt.Sprintf(`
  2818            "DestinationBytesMetricsASN" : "%s",
  2819  	`, testGeoIPASN)
  2820  	}
  2821  
  2822  	tacticsConfigJSON := fmt.Sprintf(
  2823  		tacticsConfigJSONFormat,
  2824  		tacticsRequestPublicKey, tacticsRequestPrivateKey, tacticsRequestObfuscatedKey,
  2825  		burstParameters,
  2826  		destinationBytesParameters,
  2827  		tunnelProtocol,
  2828  		tunnelProtocol,
  2829  		tunnelProtocol,
  2830  		livenessTestSize, livenessTestSize, livenessTestSize, livenessTestSize,
  2831  		propagationChannelID,
  2832  		strings.ReplaceAll(testCustomHostNameRegex, `\`, `\\`),
  2833  		tunnelProtocol)
  2834  
  2835  	err := ioutil.WriteFile(tacticsConfigFilename, []byte(tacticsConfigJSON), 0600)
  2836  	if err != nil {
  2837  		t.Fatalf("error paving tactics config file: %s", err)
  2838  	}
  2839  }
  2840  
  2841  func paveBlocklistFile(t *testing.T, blocklistFilename string) {
  2842  
  2843  	blocklistContent :=
  2844  		"255.255.255.255,test-source,test-subject\n2001:db8:f75c::0951:58bc:ef22,test-source,test-subject\nexample.org,test-source,test-subject\n"
  2845  
  2846  	err := ioutil.WriteFile(blocklistFilename, []byte(blocklistContent), 0600)
  2847  	if err != nil {
  2848  		t.Fatalf("error paving blocklist file: %s", err)
  2849  	}
  2850  }
  2851  
  2852  type pruneServerEntryTestCase struct {
  2853  	IPAddress         string
  2854  	ExplicitTag       bool
  2855  	ExpectedTag       string
  2856  	LocalTimestamp    string
  2857  	PsinetValid       bool
  2858  	ExpectPrune       bool
  2859  	IsEmbedded        bool
  2860  	DialPort0         bool
  2861  	ServerEntryFields protocol.ServerEntryFields
  2862  }
  2863  
  2864  func initializePruneServerEntriesTest(
  2865  	t *testing.T,
  2866  	runConfig *runServerConfig) ([]*pruneServerEntryTestCase, []string, int) {
  2867  
  2868  	if !runConfig.doPruneServerEntries {
  2869  		return nil, nil, 0
  2870  	}
  2871  
  2872  	newTimeStamp := time.Now().UTC().Format(time.RFC3339)
  2873  	oldTimeStamp := time.Now().Add(-30 * 24 * time.Hour).UTC().Format(time.RFC3339)
  2874  
  2875  	// Test Cases:
  2876  	// - ExplicitTag: server entry includes a tag; vs. generate a derived tag
  2877  	// - LocalTimestamp: server entry is sufficiently old to be pruned; vs. not
  2878  	// - PsinetValid: server entry is reported valid by psinet; vs. deleted
  2879  	// - ExpectPrune: prune outcome based on flags above
  2880  	// - IsEmbedded: pruned embedded server entries leave a tombstone and cannot
  2881  	//   be reimported
  2882  	// - DialPort0: set dial port to 0, a special prune case (see statusAPIRequestHandler)
  2883  
  2884  	pruneServerEntryTestCases := []*pruneServerEntryTestCase{
  2885  		&pruneServerEntryTestCase{IPAddress: "192.0.2.1", ExplicitTag: true, LocalTimestamp: newTimeStamp, PsinetValid: true, ExpectPrune: false},
  2886  		&pruneServerEntryTestCase{IPAddress: "192.0.2.2", ExplicitTag: false, LocalTimestamp: newTimeStamp, PsinetValid: true, ExpectPrune: false},
  2887  		&pruneServerEntryTestCase{IPAddress: "192.0.2.3", ExplicitTag: true, LocalTimestamp: oldTimeStamp, PsinetValid: true, ExpectPrune: false},
  2888  		&pruneServerEntryTestCase{IPAddress: "192.0.2.4", ExplicitTag: false, LocalTimestamp: oldTimeStamp, PsinetValid: true, ExpectPrune: false},
  2889  		&pruneServerEntryTestCase{IPAddress: "192.0.2.5", ExplicitTag: true, LocalTimestamp: newTimeStamp, PsinetValid: false, ExpectPrune: false},
  2890  		&pruneServerEntryTestCase{IPAddress: "192.0.2.6", ExplicitTag: false, LocalTimestamp: newTimeStamp, PsinetValid: false, ExpectPrune: false},
  2891  		&pruneServerEntryTestCase{IPAddress: "192.0.2.7", ExplicitTag: true, LocalTimestamp: oldTimeStamp, PsinetValid: false, ExpectPrune: true, IsEmbedded: false},
  2892  		&pruneServerEntryTestCase{IPAddress: "192.0.2.8", ExplicitTag: false, LocalTimestamp: oldTimeStamp, PsinetValid: false, ExpectPrune: true, IsEmbedded: false},
  2893  		&pruneServerEntryTestCase{IPAddress: "192.0.2.9", ExplicitTag: true, LocalTimestamp: oldTimeStamp, PsinetValid: false, ExpectPrune: true, IsEmbedded: true},
  2894  		&pruneServerEntryTestCase{IPAddress: "192.0.2.10", ExplicitTag: false, LocalTimestamp: oldTimeStamp, PsinetValid: false, ExpectPrune: true, IsEmbedded: true},
  2895  		&pruneServerEntryTestCase{IPAddress: "192.0.2.11", ExplicitTag: true, LocalTimestamp: oldTimeStamp, PsinetValid: true, ExpectPrune: true, IsEmbedded: false, DialPort0: true},
  2896  		&pruneServerEntryTestCase{IPAddress: "192.0.2.12", ExplicitTag: false, LocalTimestamp: oldTimeStamp, PsinetValid: true, ExpectPrune: true, IsEmbedded: true, DialPort0: true},
  2897  		&pruneServerEntryTestCase{IPAddress: "192.0.2.13", ExplicitTag: true, LocalTimestamp: oldTimeStamp, PsinetValid: true, ExpectPrune: true, IsEmbedded: true, DialPort0: true},
  2898  	}
  2899  
  2900  	for _, testCase := range pruneServerEntryTestCases {
  2901  
  2902  		dialPort := 4000
  2903  		if testCase.DialPort0 {
  2904  			dialPort = 0
  2905  		}
  2906  
  2907  		_, _, _, _, encodedServerEntry, err := GenerateConfig(
  2908  			&GenerateConfigParams{
  2909  				ServerIPAddress:     testCase.IPAddress,
  2910  				WebServerPort:       8000,
  2911  				TunnelProtocolPorts: map[string]int{runConfig.tunnelProtocol: dialPort},
  2912  			})
  2913  		if err != nil {
  2914  			t.Fatalf("GenerateConfig failed: %s", err)
  2915  		}
  2916  
  2917  		serverEntrySource := protocol.SERVER_ENTRY_SOURCE_REMOTE
  2918  		if testCase.IsEmbedded {
  2919  			serverEntrySource = protocol.SERVER_ENTRY_SOURCE_EMBEDDED
  2920  		}
  2921  
  2922  		serverEntryFields, err := protocol.DecodeServerEntryFields(
  2923  			string(encodedServerEntry),
  2924  			testCase.LocalTimestamp,
  2925  			serverEntrySource)
  2926  		if err != nil {
  2927  			t.Fatalf("DecodeServerEntryFields failed: %s", err)
  2928  		}
  2929  
  2930  		if testCase.ExplicitTag {
  2931  			testCase.ExpectedTag = prng.Base64String(32)
  2932  			serverEntryFields.SetTag(testCase.ExpectedTag)
  2933  		} else {
  2934  			testCase.ExpectedTag = protocol.GenerateServerEntryTag(
  2935  				serverEntryFields.GetIPAddress(),
  2936  				serverEntryFields.GetWebServerSecret())
  2937  		}
  2938  
  2939  		testCase.ServerEntryFields = serverEntryFields
  2940  	}
  2941  
  2942  	psinetValidServerEntryTags := make([]string, 0)
  2943  	expectedNumPruneNotices := 0
  2944  
  2945  	for _, testCase := range pruneServerEntryTestCases {
  2946  
  2947  		if testCase.PsinetValid {
  2948  			psinetValidServerEntryTags = append(
  2949  				psinetValidServerEntryTags, testCase.ExpectedTag)
  2950  		}
  2951  
  2952  		if testCase.ExpectPrune {
  2953  			expectedNumPruneNotices += 1
  2954  		}
  2955  	}
  2956  
  2957  	return pruneServerEntryTestCases,
  2958  		psinetValidServerEntryTags,
  2959  		expectedNumPruneNotices
  2960  }
  2961  
  2962  func storePruneServerEntriesTest(
  2963  	t *testing.T,
  2964  	runConfig *runServerConfig,
  2965  	testDataDirName string,
  2966  	pruneServerEntryTestCases []*pruneServerEntryTestCase) {
  2967  
  2968  	if !runConfig.doPruneServerEntries {
  2969  		return
  2970  	}
  2971  
  2972  	for _, testCase := range pruneServerEntryTestCases {
  2973  
  2974  		err := psiphon.StoreServerEntry(testCase.ServerEntryFields, true)
  2975  		if err != nil {
  2976  			t.Fatalf("StoreServerEntry failed: %s", err)
  2977  		}
  2978  	}
  2979  
  2980  	clientConfig := &psiphon.Config{
  2981  		SponsorId:            "0",
  2982  		PropagationChannelId: "0",
  2983  
  2984  		// DataRootDirectory must to be set to avoid a migration in the current
  2985  		// working directory.
  2986  		DataRootDirectory: testDataDirName,
  2987  	}
  2988  	err := clientConfig.Commit(false)
  2989  	if err != nil {
  2990  		t.Fatalf("Commit failed: %s", err)
  2991  	}
  2992  
  2993  	resolver := psiphon.NewResolver(clientConfig, true)
  2994  	defer resolver.Stop()
  2995  	clientConfig.SetResolver(resolver)
  2996  
  2997  	applyParameters := make(map[string]interface{})
  2998  	applyParameters[parameters.RecordFailedTunnelPersistentStatsProbability] = 1.0
  2999  
  3000  	err = clientConfig.SetParameters("", true, applyParameters)
  3001  	if err != nil {
  3002  		t.Fatalf("SetParameters failed: %s", err)
  3003  	}
  3004  
  3005  	verifyTestCasesStored := make(verifyTestCasesStoredLookup)
  3006  	for _, testCase := range pruneServerEntryTestCases {
  3007  		verifyTestCasesStored.mustBeStored(testCase.IPAddress)
  3008  	}
  3009  
  3010  	scanServerEntries(t, clientConfig, pruneServerEntryTestCases, func(
  3011  		t *testing.T,
  3012  		testCase *pruneServerEntryTestCase,
  3013  		serverEntry *protocol.ServerEntry) {
  3014  
  3015  		verifyTestCasesStored.isStored(testCase.IPAddress)
  3016  
  3017  		// Check that random tag was retained or derived tag was calculated as
  3018  		// expected
  3019  
  3020  		if serverEntry.Tag != testCase.ExpectedTag {
  3021  			t.Fatalf("unexpected tag for %s got %s expected %s",
  3022  				testCase.IPAddress, serverEntry.Tag, testCase.ExpectedTag)
  3023  		}
  3024  
  3025  		// Create failed tunnel event records to exercise pruning
  3026  
  3027  		dialParams, err := psiphon.MakeDialParameters(
  3028  			clientConfig,
  3029  			nil,
  3030  			func(_ *protocol.ServerEntry, _ string) bool { return true },
  3031  			func(serverEntry *protocol.ServerEntry) (string, bool) {
  3032  				return runConfig.tunnelProtocol, true
  3033  			},
  3034  			serverEntry,
  3035  			false,
  3036  			0,
  3037  			0)
  3038  		if err != nil {
  3039  			t.Fatalf("MakeDialParameters failed: %s", err)
  3040  		}
  3041  
  3042  		err = psiphon.RecordFailedTunnelStat(
  3043  			clientConfig, dialParams, nil, 0, 0, std_errors.New("test error"))
  3044  		if err != nil {
  3045  			t.Fatalf("RecordFailedTunnelStat failed: %s", err)
  3046  		}
  3047  	})
  3048  
  3049  	verifyTestCasesStored.checkStored(
  3050  		t, "missing prune test case server entries")
  3051  }
  3052  
  3053  func checkPruneServerEntriesTest(
  3054  	t *testing.T,
  3055  	runConfig *runServerConfig,
  3056  	testDataDirName string,
  3057  	pruneServerEntryTestCases []*pruneServerEntryTestCase) {
  3058  
  3059  	if !runConfig.doPruneServerEntries {
  3060  		return
  3061  	}
  3062  
  3063  	clientConfig := &psiphon.Config{
  3064  		SponsorId:            "0",
  3065  		PropagationChannelId: "0",
  3066  
  3067  		// DataRootDirectory must to be set to avoid a migration in the current
  3068  		// working directory.
  3069  		DataRootDirectory: testDataDirName,
  3070  	}
  3071  	err := clientConfig.Commit(false)
  3072  	if err != nil {
  3073  		t.Fatalf("Commit failed: %s", err)
  3074  	}
  3075  
  3076  	// Check that server entries remain or are pruned as expected
  3077  
  3078  	verifyTestCasesStored := make(verifyTestCasesStoredLookup)
  3079  	for _, testCase := range pruneServerEntryTestCases {
  3080  		if !testCase.ExpectPrune {
  3081  			verifyTestCasesStored.mustBeStored(testCase.IPAddress)
  3082  		}
  3083  	}
  3084  
  3085  	scanServerEntries(t, clientConfig, pruneServerEntryTestCases, func(
  3086  		t *testing.T,
  3087  		testCase *pruneServerEntryTestCase,
  3088  		serverEntry *protocol.ServerEntry) {
  3089  
  3090  		if testCase.ExpectPrune {
  3091  			t.Fatalf("expected prune for %s", testCase.IPAddress)
  3092  		} else {
  3093  			verifyTestCasesStored.isStored(testCase.IPAddress)
  3094  		}
  3095  	})
  3096  
  3097  	verifyTestCasesStored.checkStored(
  3098  		t, "missing prune test case server entries")
  3099  
  3100  	// Check that pruned server entries reimport or not, as expected
  3101  
  3102  	for _, testCase := range pruneServerEntryTestCases {
  3103  
  3104  		err := psiphon.StoreServerEntry(testCase.ServerEntryFields, true)
  3105  		if err != nil {
  3106  			t.Fatalf("StoreServerEntry failed: %s", err)
  3107  		}
  3108  	}
  3109  
  3110  	verifyTestCasesStored = make(verifyTestCasesStoredLookup)
  3111  	for _, testCase := range pruneServerEntryTestCases {
  3112  		if !testCase.ExpectPrune || !testCase.IsEmbedded {
  3113  			verifyTestCasesStored.mustBeStored(testCase.IPAddress)
  3114  		}
  3115  	}
  3116  
  3117  	scanServerEntries(t, clientConfig, pruneServerEntryTestCases, func(
  3118  		t *testing.T,
  3119  		testCase *pruneServerEntryTestCase,
  3120  		serverEntry *protocol.ServerEntry) {
  3121  
  3122  		if testCase.ExpectPrune && testCase.IsEmbedded {
  3123  			t.Fatalf("expected tombstone for %s", testCase.IPAddress)
  3124  		} else {
  3125  			verifyTestCasesStored.isStored(testCase.IPAddress)
  3126  		}
  3127  	})
  3128  
  3129  	verifyTestCasesStored.checkStored(
  3130  		t, "missing reimported prune test case server entries")
  3131  
  3132  	// Non-embedded server entries with tombstones _can_ be reimported
  3133  
  3134  	for _, testCase := range pruneServerEntryTestCases {
  3135  
  3136  		testCase.ServerEntryFields.SetLocalSource(protocol.SERVER_ENTRY_SOURCE_REMOTE)
  3137  
  3138  		err := psiphon.StoreServerEntry(testCase.ServerEntryFields, true)
  3139  		if err != nil {
  3140  			t.Fatalf("StoreServerEntry failed: %s", err)
  3141  		}
  3142  	}
  3143  
  3144  	verifyTestCasesStored = make(verifyTestCasesStoredLookup)
  3145  	for _, testCase := range pruneServerEntryTestCases {
  3146  		verifyTestCasesStored.mustBeStored(testCase.IPAddress)
  3147  	}
  3148  
  3149  	scanServerEntries(t, clientConfig, pruneServerEntryTestCases, func(
  3150  		t *testing.T,
  3151  		testCase *pruneServerEntryTestCase,
  3152  		serverEntry *protocol.ServerEntry) {
  3153  
  3154  		verifyTestCasesStored.isStored(testCase.IPAddress)
  3155  	})
  3156  
  3157  	verifyTestCasesStored.checkStored(
  3158  		t, "missing non-embedded reimported prune test case server entries")
  3159  }
  3160  
  3161  func scanServerEntries(
  3162  	t *testing.T,
  3163  	clientConfig *psiphon.Config,
  3164  	pruneServerEntryTestCases []*pruneServerEntryTestCase,
  3165  	scanner func(
  3166  		t *testing.T,
  3167  		testCase *pruneServerEntryTestCase,
  3168  		serverEntry *protocol.ServerEntry)) {
  3169  
  3170  	_, iterator, err := psiphon.NewServerEntryIterator(clientConfig)
  3171  	if err != nil {
  3172  		t.Fatalf("NewServerEntryIterator failed: %s", err)
  3173  	}
  3174  	defer iterator.Close()
  3175  
  3176  	for {
  3177  
  3178  		serverEntry, err := iterator.Next()
  3179  		if err != nil {
  3180  			t.Fatalf("ServerIterator.Next failed: %s", err)
  3181  		}
  3182  		if serverEntry == nil {
  3183  			break
  3184  		}
  3185  
  3186  		for _, testCase := range pruneServerEntryTestCases {
  3187  			if testCase.IPAddress == serverEntry.IpAddress {
  3188  				scanner(t, testCase, serverEntry)
  3189  				break
  3190  			}
  3191  		}
  3192  	}
  3193  }
  3194  
  3195  type verifyTestCasesStoredLookup map[string]bool
  3196  
  3197  func (v verifyTestCasesStoredLookup) mustBeStored(s string) {
  3198  	v[s] = true
  3199  }
  3200  
  3201  func (v verifyTestCasesStoredLookup) isStored(s string) {
  3202  	delete(v, s)
  3203  }
  3204  
  3205  func (v verifyTestCasesStoredLookup) checkStored(t *testing.T, errMessage string) {
  3206  	if len(v) != 0 {
  3207  		t.Fatalf("%s: %+v", errMessage, v)
  3208  	}
  3209  }