diff options
| author | Randall Meyer <[email protected]> | 2023-02-15 14:17:00 -0800 |
|---|---|---|
| committer | Randall Meyer <[email protected]> | 2023-02-22 09:18:25 -0800 |
| commit | bfa2e2b0fa93b6059fba0581b52d6d60a53b5a4a (patch) | |
| tree | ff8a9707caf844be368106ee8000ce7c6e0b57db | |
| parent | aba993ed378297f48ff6be18b17c6a963d3fd190 (diff) | |
new flag: --connect-to
Allows user to override DNS for the initial config request. This is
accomplished using a custom DialContext overring the hostname used to
Dial to. This allows for TLS certificate validation to still
happen(optionally) while connecting to TLS secured resources.
Also,
- allows for optional enforcement of certificate verification
- stamp built git version into binary and adds a --version option
- adds a user-agent to all outgoing request
- exit(1) on failures for easier shell error detection
| -rw-r--r-- | Makefile | 6 | ||||
| -rw-r--r-- | config/config.go | 62 | ||||
| -rw-r--r-- | constants/constants.go | 2 | ||||
| -rw-r--r-- | lgc/lgc.go | 109 | ||||
| -rw-r--r-- | networkQuality.go | 55 | ||||
| -rw-r--r-- | rpm/rpm.go | 22 | ||||
| -rw-r--r-- | utilities/transport.go | 46 | ||||
| -rw-r--r-- | utilities/utilities.go | 12 |
8 files changed, 188 insertions, 126 deletions
@@ -1,6 +1,10 @@ +PKG := github.com/network-quality/goresponsiveness +GIT_VERSION := $(shell git describe --always --long) +LDFLAGS := -ldflags "-X $(PKG)/utilities.GitVersion=$(GIT_VERSION)" + all: build test build: - go build networkQuality.go + go build $(LDFLAGS) networkQuality.go test: go test ./timeoutat/ ./traceable/ ./ms/ ./utilities/ golines: diff --git a/config/config.go b/config/config.go index f223ec5..5c4fb8d 100644 --- a/config/config.go +++ b/config/config.go @@ -24,53 +24,59 @@ import ( "strings" "github.com/network-quality/goresponsiveness/utilities" - "golang.org/x/net/http2" ) type ConfigUrls struct { - SmallUrl string `json:"small_https_download_url"` - SmallUrlHost string - LargeUrl string `json:"large_https_download_url"` - LargeUrlHost string - UploadUrl string `json:"https_upload_url"` - UploadUrlHost string + SmallUrl string `json:"small_https_download_url"` + LargeUrl string `json:"large_https_download_url"` + UploadUrl string `json:"https_upload_url"` } type Config struct { Version int Urls ConfigUrls `json:"urls"` Source string - Test_Endpoint string + ConnectToAddr string `json:"test_endpoint"` } -func (c *Config) Get(configHost string, configPath string, keyLogger io.Writer) error { - configTransport := http2.Transport{} - configTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - +func (c *Config) Get(configHost string, configPath string, insecureSkipVerify bool, keyLogger io.Writer) error { + configTransport := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: insecureSkipVerify, + }, + Proxy: http.ProxyFromEnvironment, + } if !utilities.IsInterfaceNil(keyLogger) { configTransport.TLSClientConfig.KeyLogWriter = keyLogger } - configClient := &http.Client{Transport: &configTransport} + + utilities.OverrideHostTransport(configTransport, c.ConnectToAddr) + + configClient := &http.Client{Transport: configTransport} + // Extraneous /s in URLs is normally okay, but the Apple CDN does not // like them. Make sure that we put exactly one (1) / between the host // and the path. if !strings.HasPrefix(configPath, "/") { configPath = "/" + configPath } + c.Source = fmt.Sprintf("https://%s%s", configHost, configPath) req, err := http.NewRequest("GET", c.Source, nil) if err != nil { return fmt.Errorf( - "Error: Could not create request for configuration host %s: %v\n", + "Error: Could not create request for configuration host %s: %v", configHost, err, ) } + req.Header.Set("User-Agent", utilities.UserAgent()) + resp, err := configClient.Do(req) if err != nil { return fmt.Errorf( - "could not connect to configuration host %s: %v", + "Error: could not connect to configuration host %s: %v", configHost, err, ) @@ -79,7 +85,7 @@ func (c *Config) Get(configHost string, configPath string, keyLogger io.Writer) if resp.StatusCode != 200 { return fmt.Errorf( - "Error: Configuration host %s returned %d for config request\n", + "Error: Configuration host %s returned %d for config request", configHost, resp.StatusCode, ) @@ -88,7 +94,7 @@ func (c *Config) Get(configHost string, configPath string, keyLogger io.Writer) jsonConfig, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf( - "could not read configuration content downloaded from %s: %v", + "Error: Could not read configuration content downloaded from %s: %v", c.Source, err, ) @@ -103,26 +109,6 @@ func (c *Config) Get(configHost string, configPath string, keyLogger io.Writer) ) } - if len(c.Test_Endpoint) != 0 { - tempUrl, err := url.Parse(c.Urls.LargeUrl) - if err != nil { - return fmt.Errorf("error parsing large_https_download_url: %v", err) - } - c.Urls.LargeUrl = tempUrl.Scheme + "://" + c.Test_Endpoint + "" + tempUrl.Path - c.Urls.LargeUrlHost = tempUrl.Host - tempUrl, err = url.Parse(c.Urls.SmallUrl) - if err != nil { - return fmt.Errorf("error parsing small_https_download_url: %v", err) - } - c.Urls.SmallUrl = tempUrl.Scheme + "://" + c.Test_Endpoint + "" + tempUrl.Path - c.Urls.SmallUrlHost = tempUrl.Host - tempUrl, err = url.Parse(c.Urls.UploadUrl) - if err != nil { - return fmt.Errorf("error parsing https_upload_url: %v", err) - } - c.Urls.UploadUrl = tempUrl.Scheme + "://" + c.Test_Endpoint + "" + tempUrl.Path - c.Urls.UploadUrlHost = tempUrl.Host - } return nil } @@ -133,7 +119,7 @@ func (c *Config) String() string { c.Urls.SmallUrl, c.Urls.LargeUrl, c.Urls.UploadUrl, - c.Test_Endpoint, + c.ConnectToAddr, ) } diff --git a/constants/constants.go b/constants/constants.go index 8320295..66f7110 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -46,4 +46,6 @@ var ( DefaultDebug bool = false // The default URL for the config host. DefaultConfigHost string = "networkquality.example.com" + // The default determination of whether to verify server certificates + DefaultInsecureSkipVerify bool = true ) @@ -29,7 +29,6 @@ import ( "github.com/network-quality/goresponsiveness/stats" "github.com/network-quality/goresponsiveness/traceable" "github.com/network-quality/goresponsiveness/utilities" - "golang.org/x/net/http2" ) type LoadGeneratingConnection interface { @@ -53,19 +52,20 @@ func NewLoadGeneratingConnectionCollection() LoadGeneratingConnectionCollection // TODO: All 64-bit fields that are accessed atomically must // appear at the top of this struct. type LoadGeneratingConnectionDownload struct { - downloaded uint64 - lastIntervalEnd int64 - Path string - Host string - downloadStartTime time.Time - lastDownloaded uint64 - client *http.Client - debug debug.DebugLevel - valid bool - KeyLogger io.Writer - clientId uint64 - tracer *httptrace.ClientTrace - stats stats.TraceStats + downloaded uint64 + lastIntervalEnd int64 + ConnectToAddr string + URL string + downloadStartTime time.Time + lastDownloaded uint64 + client *http.Client + debug debug.DebugLevel + valid bool + InsecureSkipVerify bool + KeyLogger io.Writer + clientId uint64 + tracer *httptrace.ClientTrace + stats stats.TraceStats } func (lgd *LoadGeneratingConnectionDownload) SetDnsStartTimeInfo( @@ -257,8 +257,13 @@ func (lgd *LoadGeneratingConnectionDownload) Start( lgd.downloaded = 0 lgd.debug = debugLevel lgd.clientId = utilities.GenerateUniqueId() - transport := http2.Transport{} - transport.TLSClientConfig = &tls.Config{} + + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: lgd.InsecureSkipVerify, + }, + } if !utilities.IsInterfaceNil(lgd.KeyLogger) { if debug.IsDebug(lgd.debug) { @@ -276,9 +281,11 @@ func (lgd *LoadGeneratingConnectionDownload) Start( // https://github.com/golang/go/blob/7ca6902c171b336d98adbb103d701a013229c806/src/net/http/transport.go#L74 transport.TLSClientConfig.KeyLogWriter = lgd.KeyLogger } - transport.TLSClientConfig.InsecureSkipVerify = true + transport.TLSClientConfig.InsecureSkipVerify = lgd.InsecureSkipVerify + + utilities.OverrideHostTransport(transport, lgd.ConnectToAddr) - lgd.client = &http.Client{Transport: &transport} + lgd.client = &http.Client{Transport: transport} lgd.valid = true lgd.tracer = traceable.GenerateHttpTimingTracer(lgd, lgd.debug) @@ -309,26 +316,16 @@ func (lgd *LoadGeneratingConnectionDownload) doDownload(ctx context.Context) { if request, err = http.NewRequestWithContext( httptrace.WithClientTrace(ctx, lgd.tracer), "GET", - lgd.Path, + lgd.URL, nil, ); err != nil { lgd.valid = false return } - // To support test_endpoint - if len(lgd.Host) != 0 { - if debug.IsDebug(lgd.debug) { - fmt.Printf( - "Because of a test_endpoint in the config, there is a special Host set for this connection: %s\n", - lgd.Host, - ) - } - request.Host = lgd.Host - } - // Used to disable compression request.Header.Set("Accept-Encoding", "identity") + request.Header.Set("User-Agent", utilities.UserAgent()) lgd.downloadStartTime = time.Now() lgd.lastIntervalEnd = 0 @@ -355,17 +352,18 @@ func (lgd *LoadGeneratingConnectionDownload) doDownload(ctx context.Context) { // TODO: All 64-bit fields that are accessed atomically must // appear at the top of this struct. type LoadGeneratingConnectionUpload struct { - uploaded uint64 - lastIntervalEnd int64 - Path string - Host string - uploadStartTime time.Time - lastUploaded uint64 - client *http.Client - debug debug.DebugLevel - valid bool - KeyLogger io.Writer - clientId uint64 + uploaded uint64 + lastIntervalEnd int64 + URL string + ConnectToAddr string + uploadStartTime time.Time + lastUploaded uint64 + client *http.Client + debug debug.DebugLevel + valid bool + InsecureSkipVerify bool + KeyLogger io.Writer + clientId uint64 } func (lgu *LoadGeneratingConnectionUpload) ClientId() uint64 { @@ -416,26 +414,16 @@ func (lgu *LoadGeneratingConnectionUpload) doUpload(ctx context.Context) bool { if request, err = http.NewRequest( "POST", - lgu.Path, + lgu.URL, s, ); err != nil { lgu.valid = false return false } - // To support test_endpoint - if len(lgu.Host) != 0 { - if debug.IsDebug(lgu.debug) { - fmt.Printf( - "Because of a test_endpoint in the config, there is a special Host set for this connection: %s\n", - lgu.Host, - ) - } - request.Host = lgu.Host - } - // Used to disable compression request.Header.Set("Accept-Encoding", "identity") + request.Header.Set("User-Agent", utilities.UserAgent()) lgu.uploadStartTime = time.Now() lgu.lastIntervalEnd = 0 @@ -460,10 +448,12 @@ func (lgu *LoadGeneratingConnectionUpload) Start( lgu.clientId = utilities.GenerateUniqueId() lgu.debug = debugLevel - // See above for the rationale of doing http2.Transport{} here - // to ensure that we are using h2. - transport := http2.Transport{} - transport.TLSClientConfig = &tls.Config{} + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: lgu.InsecureSkipVerify, + }, + } if !utilities.IsInterfaceNil(lgu.KeyLogger) { if debug.IsDebug(lgu.debug) { @@ -473,9 +463,10 @@ func (lgu *LoadGeneratingConnectionUpload) Start( } transport.TLSClientConfig.KeyLogWriter = lgu.KeyLogger } - transport.TLSClientConfig.InsecureSkipVerify = true - lgu.client = &http.Client{Transport: &transport} + utilities.OverrideHostTransport(transport, lgu.ConnectToAddr) + + lgu.client = &http.Client{Transport: transport} lgu.valid = true if debug.IsDebug(lgu.debug) { diff --git a/networkQuality.go b/networkQuality.go index 5d9cea4..ef7543d 100644 --- a/networkQuality.go +++ b/networkQuality.go @@ -95,16 +95,36 @@ var ( 100, "Time (in ms) between probes (foreign and self).", ) + connectToAddr = flag.String( + "connect-to", + "", + "address (hostname or IP) to connect to (overriding DNS). Disabled by default.", + ) + insecureSkipVerify = flag.Bool( + "insecure-skip-verify", + constants.DefaultInsecureSkipVerify, + "Enable server certificate validation.", + ) prometheusStatsFilename = flag.String( "prometheus-stats-filename", "", "If filename specified, prometheus stats will be written. If specified file exists, it will be overwritten.", ) + showVersion = flag.Bool( + "version", + false, + "Show version.", + ) ) func main() { flag.Parse() + if *showVersion { + fmt.Fprintf(os.Stdout, "goresponsiveness %s\n", utilities.GitVersion) + os.Exit(0) + } + timeoutDuration := time.Second * time.Duration(*rpmtimeout) timeoutAbsoluteTime := time.Now().Add(timeoutDuration) @@ -143,7 +163,9 @@ func main() { // all the network connections that are responsible for generating the load. networkActivityCtx, networkActivityCtxCancel := context.WithCancel(operatingCtx) - config := &config.Config{} + config := &config.Config{ + ConnectToAddr: *connectToAddr, + } var debugLevel debug.DebugLevel = debug.Error if *debugCliFlag { @@ -176,9 +198,9 @@ func main() { } } - if err := config.Get(configHostPort, *configPath, sslKeyFileConcurrentWriter); err != nil { + if err := config.Get(configHostPort, *configPath, *insecureSkipVerify, sslKeyFileConcurrentWriter); err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) - return + os.Exit(1) } if err := config.IsValid(); err != nil { fmt.Fprintf( @@ -187,7 +209,7 @@ func main() { config.Source, err, ) - return + os.Exit(1) } if debug.IsDebug(debugLevel) { fmt.Printf("Configuration: %s\n", config) @@ -219,7 +241,7 @@ func main() { *profile, err, ) - return + os.Exit(1) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() @@ -332,31 +354,34 @@ func main() { */ generate_lgd := func() lgc.LoadGeneratingConnection { return &lgc.LoadGeneratingConnectionDownload{ - Path: config.Urls.LargeUrl, - Host: config.Urls.LargeUrlHost, - KeyLogger: sslKeyFileConcurrentWriter, + URL: config.Urls.LargeUrl, + KeyLogger: sslKeyFileConcurrentWriter, + ConnectToAddr: config.ConnectToAddr, + InsecureSkipVerify: *insecureSkipVerify, } } generate_lgu := func() lgc.LoadGeneratingConnection { return &lgc.LoadGeneratingConnectionUpload{ - Path: config.Urls.UploadUrl, - Host: config.Urls.UploadUrlHost, - KeyLogger: sslKeyFileConcurrentWriter, + URL: config.Urls.UploadUrl, + KeyLogger: sslKeyFileConcurrentWriter, + ConnectToAddr: config.ConnectToAddr, } } generateSelfProbeConfiguration := func() rpm.ProbeConfiguration { return rpm.ProbeConfiguration{ - URL: config.Urls.SmallUrl, - Host: config.Urls.SmallUrlHost, + URL: config.Urls.SmallUrl, + ConnectToAddr: config.ConnectToAddr, + InsecureSkipVerify: *insecureSkipVerify, } } generateForeignProbeConfiguration := func() rpm.ProbeConfiguration { return rpm.ProbeConfiguration{ - URL: config.Urls.SmallUrl, - Host: config.Urls.SmallUrlHost, + URL: config.Urls.SmallUrl, + ConnectToAddr: config.ConnectToAddr, + InsecureSkipVerify: *insecureSkipVerify, } } @@ -32,7 +32,6 @@ import ( "github.com/network-quality/goresponsiveness/stats" "github.com/network-quality/goresponsiveness/traceable" "github.com/network-quality/goresponsiveness/utilities" - "golang.org/x/net/http2" ) func addFlows( @@ -61,8 +60,10 @@ func addFlows( } type ProbeConfiguration struct { - URL string - Host string + ConnectToAddr string + URL string + Host string + InsecureSkipVerify bool } type ProbeDataPoint struct { @@ -157,12 +158,9 @@ func Probe( return err } - // To support test_endpoint - if len(probeHost) != 0 { - probe_req.Host = probeHost - } // Used to disable compression probe_req.Header.Set("Accept-Encoding", "identity") + probe_req.Header.Set("User-Agent", utilities.UserAgent()) probe_resp, err := client.Do(probe_req) if err != nil { @@ -292,8 +290,9 @@ func CombinedProber( probeCount+1, ) } - transport := http2.Transport{} + transport := &http.Transport{} transport.TLSClientConfig = &tls.Config{} + transport.Proxy = http.ProxyFromEnvironment if !utilities.IsInterfaceNil(keyLogger) { if debug.IsDebug(debugging.Level) { @@ -311,9 +310,12 @@ func CombinedProber( // https://github.com/golang/go/blob/7ca6902c171b336d98adbb103d701a013229c806/src/net/http/transport.go#L74 transport.TLSClientConfig.KeyLogWriter = keyLogger } - transport.TLSClientConfig.InsecureSkipVerify = true - foreignProbeClient := &http.Client{Transport: &transport} + transport.TLSClientConfig.InsecureSkipVerify = foreignProbeConfiguration.InsecureSkipVerify + + utilities.OverrideHostTransport(transport, foreignProbeConfiguration.ConnectToAddr) + + foreignProbeClient := &http.Client{Transport: transport} // Start Foreign Connection Prober probeCount++ diff --git a/utilities/transport.go b/utilities/transport.go new file mode 100644 index 0000000..2d70989 --- /dev/null +++ b/utilities/transport.go @@ -0,0 +1,46 @@ +/* + * This file is part of Go Responsiveness. + * + * Go Responsiveness is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * either version 2 of the License, or (at your option) any later version. + * Go Responsiveness is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with Go Responsiveness. If not, see <https://www.gnu.org/licenses/>. + */ + +package utilities + +import ( + "context" + "net" + "net/http" + "time" + + "golang.org/x/net/http2" +) + +func OverrideHostTransport(transport *http.Transport, connectToAddr string) { + dialer := &net.Dialer{ + Timeout: 10 * time.Second, + } + + transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + _, port, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + + if len(connectToAddr) > 0 { + addr = net.JoinHostPort(connectToAddr, port) + } + + return dialer.DialContext(ctx, network, addr) + } + + http2.ConfigureTransport(transport) + +} diff --git a/utilities/utilities.go b/utilities/utilities.go index 57b4a90..377be56 100644 --- a/utilities/utilities.go +++ b/utilities/utilities.go @@ -28,6 +28,11 @@ import ( "golang.org/x/exp/constraints" ) +var ( + // GitVersion is the Git revision hash + GitVersion = "dev" +) + func Iota(low int, high int) (made []int) { made = make([]int, high-low) @@ -46,9 +51,6 @@ func SignedPercentDifference[T constraints.Float | constraints.Integer]( current T, previous T, ) (difference float64) { - //return ((current - previous) / (float64(current+previous) / 2.0)) * float64( - //100, - // ) fCurrent := float64(current) fPrevious := float64(previous) return ((fCurrent - fPrevious) / fPrevious) * 100.0 @@ -203,3 +205,7 @@ func ApproximatelyEqual[T float32 | float64](truth T, maybe T, fudge T) bool { diff := math.Abs((bTruth - bMaybe)) return diff < bFudge } + +func UserAgent() string { + return fmt.Sprintf("goresponsiveness/%s", GitVersion) +} |
