From 0c2391d9db9ca1e199050ba91ef8d5cf508b9b95 Mon Sep 17 00:00:00 2001 From: Randall Meyer Date: Wed, 15 Feb 2023 09:58:12 -0800 Subject: Update dependencies --- go.mod | 8 ++++---- go.sum | 18 ++++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 3956ad9..a448c75 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,11 @@ module github.com/network-quality/goresponsiveness go 1.18 require ( - golang.org/x/net v0.0.0-20220225172249-27dd8689420f - golang.org/x/sys v0.1.0 + golang.org/x/net v0.7.0 + golang.org/x/sys v0.5.0 ) require ( - golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 - golang.org/x/text v0.3.7 // indirect + golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb + golang.org/x/text v0.7.0 // indirect ) diff --git a/go.sum b/go.sum index c642a0b..3a4ddff 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,8 @@ -golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE= -golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w= +golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -- cgit v1.2.3 From ea1c43eb259aeefa340bdf99339f4e76bc744e4f Mon Sep 17 00:00:00 2001 From: Randall Meyer Date: Fri, 10 Feb 2023 14:37:56 -0800 Subject: new flag: --url This enables passing in a single URL for easier running eg networkQuality --url https://networkquality.example.com/path/to/config --- networkQuality.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/networkQuality.go b/networkQuality.go index 1af90bb..12aa724 100644 --- a/networkQuality.go +++ b/networkQuality.go @@ -18,6 +18,7 @@ import ( "context" "flag" "fmt" + "net/url" "os" "runtime/pprof" "time" @@ -53,6 +54,11 @@ var ( "config", "path on the server to the configuration endpoint.", ) + configURL = flag.String( + "url", + "", + "configuration URL (takes precedence over other configuration parts)", + ) debugCliFlag = flag.Bool( "debug", constants.DefaultDebug, @@ -95,7 +101,24 @@ func main() { timeoutDuration := time.Second * time.Duration(*rpmtimeout) timeoutAbsoluteTime := time.Now().Add(timeoutDuration) - configHostPort := fmt.Sprintf("%s:%d", *configHost, *configPort) + + var configHostPort string + + // if user specified a full URL, use that and set the various parts we need out of it + if len(*configURL) > 0 { + parsedURL, err := url.ParseRequestURI(*configURL) + if err != nil { + fmt.Printf("Error: Could not parse %q: %s", *configURL, err) + os.Exit(1) + } + + *configHost = parsedURL.Hostname() + *configPath = parsedURL.Path + // We don't explicitly care about configuring the *configPort. + configHostPort = parsedURL.Host // host or host:port + } else { + configHostPort = fmt.Sprintf("%s:%d", *configHost, *configPort) + } // This is the overall operating context of the program. All other // contexts descend from this one. Canceling this one cancels all -- cgit v1.2.3 From 8a66c0ad439a6a5d2ae886b28ec841b3663df226 Mon Sep 17 00:00:00 2001 From: Randall Meyer Date: Sun, 16 Oct 2022 15:20:32 -0700 Subject: extendedstats: fix build error on other platform Also, simplify code for darwin's TCPInfo --- extendedstats/darwin.go | 22 +++++++--------------- extendedstats/other.go | 2 +- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/extendedstats/darwin.go b/extendedstats/darwin.go index 59d6e38..19c5bc0 100644 --- a/extendedstats/darwin.go +++ b/extendedstats/darwin.go @@ -28,8 +28,6 @@ import ( type AggregateExtendedStats struct { Maxseg uint64 - MaxSendMss uint64 - MaxRecvMss uint64 TotalRetransmissions uint64 totalSent uint64 TotalReorderings uint64 @@ -44,12 +42,8 @@ func ExtendedStatsAvailable() bool { } type TCPInfo struct { - Rxoutoforderbytes uint64 - Txretransmitbytes uint64 - Txbytes uint64 - Rtt uint32 - Maxseg uint32 - Snd_cwnd uint32 + unix.TCPConnectionInfo + Rtt uint32 // Srtt under Darwin } func (es *AggregateExtendedStats) IncorporateConnectionStats(basicConn net.Conn) error { @@ -98,20 +92,18 @@ func GetTCPInfo(basicConn net.Conn) (*TCPInfo, error) { var rawInfo *unix.TCPConnectionInfo = nil var tcpInfo *TCPInfo = nil - rawConn.Control(func(fd uintptr) { + rerr := rawConn.Control(func(fd uintptr) { rawInfo, err = unix.GetsockoptTCPConnectionInfo( int(fd), unix.IPPROTO_TCP, unix.TCP_CONNECTION_INFO, ) }) + if rerr != nil { + return nil, rerr + } if rawInfo != nil && err == nil { - tcpInfo = &TCPInfo{} - tcpInfo.Rxoutoforderbytes = rawInfo.Rxoutoforderbytes - tcpInfo.Txretransmitbytes = rawInfo.Txretransmitbytes - tcpInfo.Rtt = rawInfo.Srtt - tcpInfo.Snd_cwnd = rawInfo.Snd_cwnd - tcpInfo.Maxseg = rawInfo.Maxseg + tcpInfo = &TCPInfo{TCPConnectionInfo: *rawInfo, Rtt: rawInfo.Srtt} } return tcpInfo, err } diff --git a/extendedstats/other.go b/extendedstats/other.go index 2d76eaf..ed6d925 100644 --- a/extendedstats/other.go +++ b/extendedstats/other.go @@ -36,6 +36,6 @@ func ExtendedStatsAvailable() bool { return false } -func GetTCPInfo(basicConn net.Conn) (interface, error) { +func GetTCPInfo(basicConn net.Conn) (interface{}, error) { return nil, fmt.Errorf("GetTCPInfo is not supported on this platform") } -- cgit v1.2.3 From e0305ad041549f5f7ff90baa9cae58cf88a9c346 Mon Sep 17 00:00:00 2001 From: Randall Meyer Date: Sun, 16 Oct 2022 15:19:20 -0700 Subject: Emit Prometheus stats if requested --- networkQuality.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/networkQuality.go b/networkQuality.go index 12aa724..5d9cea4 100644 --- a/networkQuality.go +++ b/networkQuality.go @@ -15,6 +15,7 @@ package main import ( + "bytes" "context" "flag" "fmt" @@ -94,6 +95,11 @@ var ( 100, "Time (in ms) between probes (foreign and self).", ) + prometheusStatsFilename = flag.String( + "prometheus-stats-filename", + "", + "If filename specified, prometheus stats will be written. If specified file exists, it will be overwritten.", + ) ) func main() { @@ -704,4 +710,24 @@ Trimmed Mean Foreign RTT: %f fmt.Printf("Done cooling down.\n") } + if len(*prometheusStatsFilename) > 0 { + var testStable int + if testRanToStability { + testStable = 1 + } + var buffer bytes.Buffer + buffer.WriteString(fmt.Sprintf("networkquality_test_stable %d\n", testStable)) + buffer.WriteString(fmt.Sprintf("networkquality_rpm_value %d\n", int64(p90Rpm))) + buffer.WriteString(fmt.Sprintf("networkquality_trimmed_rpm_value %d\n", int64(meanRpm))) //utilities.ToMbps(lastDownloadThroughputRate), + + buffer.WriteString(fmt.Sprintf("networkquality_download_bits_per_second %d\n", int64(lastDownloadThroughputRate))) + buffer.WriteString(fmt.Sprintf("networkquality_download_connections %d\n", int64(lastDownloadThroughputOpenConnectionCount))) + buffer.WriteString(fmt.Sprintf("networkquality_upload_bits_per_second %d\n", int64(lastUploadThroughputRate))) + buffer.WriteString(fmt.Sprintf("networkquality_upload_connections %d\n", lastUploadThroughputOpenConnectionCount)) + + if err := os.WriteFile(*prometheusStatsFilename, buffer.Bytes(), 0644); err != nil { + fmt.Printf("could not write %s: %s", *prometheusStatsFilename, err) + os.Exit(1) + } + } } -- cgit v1.2.3 From aba993ed378297f48ff6be18b17c6a963d3fd190 Mon Sep 17 00:00:00 2001 From: Randall Meyer Date: Wed, 15 Feb 2023 10:34:04 -0800 Subject: lint/deprecation cleanup --- .gitignore | 2 +- config/config.go | 23 ++++++++++++++++++++--- datalogger/logger.go | 4 ++-- lgc/lgc.go | 13 ++++++------- rpm/rpm.go | 1 + traceable/traceable_test.go | 5 ++--- utilities/utilities.go | 4 ++-- 7 files changed, 34 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 1a990f8..9449fc6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -networkQuality \ No newline at end of file +/networkQuality diff --git a/config/config.go b/config/config.go index 8d9eaa5..f223ec5 100644 --- a/config/config.go +++ b/config/config.go @@ -19,7 +19,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" "net/url" "strings" @@ -59,7 +58,16 @@ func (c *Config) Get(configHost string, configPath string, keyLogger io.Writer) configPath = "/" + configPath } c.Source = fmt.Sprintf("https://%s%s", configHost, configPath) - resp, err := configClient.Get(c.Source) + 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", + configHost, + err, + ) + } + + resp, err := configClient.Do(req) if err != nil { return fmt.Errorf( "could not connect to configuration host %s: %v", @@ -67,8 +75,17 @@ func (c *Config) Get(configHost string, configPath string, keyLogger io.Writer) err, ) } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return fmt.Errorf( + "Error: Configuration host %s returned %d for config request\n", + configHost, + resp.StatusCode, + ) + } - jsonConfig, err := ioutil.ReadAll(resp.Body) + jsonConfig, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf( "could not read configuration content downloaded from %s: %v", diff --git a/datalogger/logger.go b/datalogger/logger.go index 249a059..1f9c2d6 100644 --- a/datalogger/logger.go +++ b/datalogger/logger.go @@ -112,13 +112,13 @@ func (logger *CSVDataLogger[T]) Export() bool { visibleFields := reflect.VisibleFields(reflect.TypeOf((*T)(nil)).Elem()) for i, v := range visibleFields { description, success := v.Tag.Lookup("Description") - columnName := fmt.Sprintf("%s", v.Name) + columnName := v.Name if success { if description == "[OMIT]" { toOmit = append(toOmit, i) continue } - columnName = fmt.Sprintf("%s", description) + columnName = description } logger.destination.Write([]byte(fmt.Sprintf("%s, ", columnName))) } diff --git a/lgc/lgc.go b/lgc/lgc.go index bbab5ad..1597060 100644 --- a/lgc/lgc.go +++ b/lgc/lgc.go @@ -19,7 +19,6 @@ import ( "crypto/tls" "fmt" "io" - "io/ioutil" "net/http" "net/http/httptrace" "sync" @@ -346,7 +345,7 @@ func (lgd *LoadGeneratingConnectionDownload) doDownload(ctx context.Context) { return } cr := &countingReader{n: &lgd.downloaded, ctx: ctx, readable: get.Body} - _, _ = io.Copy(ioutil.Discard, cr) + _, _ = io.Copy(io.Discard, cr) get.Body.Close() if debug.IsDebug(lgd.debug) { fmt.Printf("Ending a load-generating download.\n") @@ -373,12 +372,12 @@ func (lgu *LoadGeneratingConnectionUpload) ClientId() uint64 { return lgu.clientId } -func (lgd *LoadGeneratingConnectionUpload) TransferredInInterval() (uint64, time.Duration) { - transferred := atomic.SwapUint64(&lgd.uploaded, 0) - newIntervalEnd := (time.Now().Sub(lgd.uploadStartTime)).Nanoseconds() - previousIntervalEnd := atomic.SwapInt64(&lgd.lastIntervalEnd, newIntervalEnd) +func (lgu *LoadGeneratingConnectionUpload) TransferredInInterval() (uint64, time.Duration) { + transferred := atomic.SwapUint64(&lgu.uploaded, 0) + newIntervalEnd := (time.Now().Sub(lgu.uploadStartTime)).Nanoseconds() + previousIntervalEnd := atomic.SwapInt64(&lgu.lastIntervalEnd, newIntervalEnd) intervalLength := time.Duration(newIntervalEnd - previousIntervalEnd) - if debug.IsDebug(lgd.debug) { + if debug.IsDebug(lgu.debug) { fmt.Printf("upload: Transferred: %v bytes in %v.\n", transferred, intervalLength) } return transferred, intervalLength diff --git a/rpm/rpm.go b/rpm/rpm.go index d5aad20..ece0082 100644 --- a/rpm/rpm.go +++ b/rpm/rpm.go @@ -11,6 +11,7 @@ * You should have received a copy of the GNU General Public License along * with Go Responsiveness. If not, see . */ + package rpm import ( diff --git a/traceable/traceable_test.go b/traceable/traceable_test.go index 1ba6f51..e9d5a74 100644 --- a/traceable/traceable_test.go +++ b/traceable/traceable_test.go @@ -18,7 +18,6 @@ import ( "context" "crypto/tls" "io" - "io/ioutil" "net/http" "net/http/httptrace" "sync" @@ -141,7 +140,7 @@ func TestDuplicativeTraceables(t *testing.T) { if err != nil { return } - _, _ = io.Copy(ioutil.Discard, get.Body) + _, _ = io.Copy(io.Discard, get.Body) get.Body.Close() }() go func() { @@ -150,7 +149,7 @@ func TestDuplicativeTraceables(t *testing.T) { if err != nil { return } - _, _ = io.Copy(ioutil.Discard, get.Body) + _, _ = io.Copy(io.Discard, get.Body) get.Body.Close() }() diff --git a/utilities/utilities.go b/utilities/utilities.go index 538889c..57b4a90 100644 --- a/utilities/utilities.go +++ b/utilities/utilities.go @@ -180,9 +180,9 @@ func OrTimeout(f func(), timeout time.Duration) { return completed }() select { - case _ = <-completeChannel: + case <-completeChannel: break - case _ = <-time.After(timeout): + case <-time.After(timeout): break } } -- cgit v1.2.3 From bfa2e2b0fa93b6059fba0581b52d6d60a53b5a4a Mon Sep 17 00:00:00 2001 From: Randall Meyer Date: Wed, 15 Feb 2023 14:17:00 -0800 Subject: 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 --- Makefile | 6 ++- config/config.go | 62 +++++++++++----------------- constants/constants.go | 2 + lgc/lgc.go | 109 +++++++++++++++++++++++-------------------------- networkQuality.go | 55 ++++++++++++++++++------- rpm/rpm.go | 22 +++++----- utilities/transport.go | 46 +++++++++++++++++++++ utilities/utilities.go | 12 ++++-- 8 files changed, 188 insertions(+), 126 deletions(-) create mode 100644 utilities/transport.go diff --git a/Makefile b/Makefile index f2df1c5..4a3db5c 100644 --- a/Makefile +++ b/Makefile @@ -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 ) diff --git a/lgc/lgc.go b/lgc/lgc.go index 1597060..0b3f075 100644 --- a/lgc/lgc.go +++ b/lgc/lgc.go @@ -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, } } diff --git a/rpm/rpm.go b/rpm/rpm.go index ece0082..3774c8f 100644 --- a/rpm/rpm.go +++ b/rpm/rpm.go @@ -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 . + */ + +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) +} -- cgit v1.2.3