### Docker Compose Setup for Local Development Source: https://context7.com/m-lab/ndt-server/llms.txt This section outlines the steps for setting up a multi-container local development environment using `docker-compose`. It covers generating TLS certificates, starting the services, rebuilding with changes, and verifying service availability. ```bash # Generate local TLS certificates docker-compose run ndt-server ./gen_local_test_certs.bash # Start all services (ndt-server + jostler sidecar) docker-compose up # After code changes, rebuild: docker-compose up --build # Verify services: # NDT7 browser test: http://localhost/ndt7.html # NDT7 TLS test: https://localhost/ndt7.html (accept self-signed cert warning) # Prometheus metrics: http://localhost:9990/metrics ``` -------------------------------- ### Client Metadata JSON Example Source: https://github.com/m-lab/ndt-server/blob/main/spec/data-format.md Example structure for client metadata. These key-value pairs are provided by the client in the query string. ```json "ClientMetadata":[ { "Name":"ClientLibraryName", "Value":"libndt7.js" }, { "Name":"ClientLibraryVersion", "Value":"0.4" } ] ``` -------------------------------- ### Start HTTP Servers Asynchronously with Ready Sockets Source: https://context7.com/m-lab/ndt-server/llms.txt Use `ListenAndServeAsync` and `ListenAndServeTLSAsync` to start HTTP/S servers in the background. These functions ensure the listening socket is fully bound before returning, preventing connection races. ```go package main import ( "log" "net/http" "github.com/m-lab/ndt-server/ndt7/listener" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) // Plain HTTP server — socket is ready to accept connections when this returns plainServer := &http.Server{Addr: ":8080", Handler: mux} if err := listener.ListenAndServeAsync(plainServer); err != nil { log.Fatalf("Could not start plain server: %v", err) } log.Println("Listening on", plainServer.Addr) // Addr updated if :0 was used // TLS server — socket is ready when this returns tlsServer := &http.Server{Addr: ":4443", Handler: mux} if err := listener.ListenAndServeTLSAsync(tlsServer, "/certs/cert.pem", "/certs/key.pem"); err != nil { log.Fatalf("Could not start TLS server: %v", err) } log.Println("TLS listening on", tlsServer.Addr) // Both servers now immediately available; block until done select {} } ``` -------------------------------- ### Start NDT Server with Docker Compose Source: https://github.com/m-lab/ndt-server/blob/main/README.md Starts the NDT server using Docker Compose. Rebuild the image with `--build` if changes are made to the server binary. ```bash docker-compose up ``` -------------------------------- ### Server Metadata JSON Example Source: https://github.com/m-lab/ndt-server/blob/main/spec/data-format.md Example structure for server metadata. These key-value pairs are derived from the '-label' flag in the deployment configuration. ```json "ServerMetadata":[ { "Name":"deployment", "Value":"stable" }, { "Name":"machine-type", "Value":"virtual" } ] ``` -------------------------------- ### Start ndt-server with Docker (Linux) Source: https://context7.com/m-lab/ndt-server/llms.txt Instructions for preparing runtime directories, generating TLS certificates, enabling TCP BBR, building the Docker image, and running the NDT server container. ```APIDOC ## Start ndt-server with Docker (Linux) The primary way to deploy ndt-server. Generates TLS certificates, builds the image, enables BBR, and runs the container with all standard ports mapped. ```bash # Prepare runtime directories install -d certs datadir # Generate self-signed TLS certificates for local testing ./gen_local_test_certs.bash # Enable TCP BBR congestion control (improves ndt7 accuracy) sudo modprobe tcp_bbr # Build Docker image docker build . -t ndt-server # Run the server # NDT7 TLS: :4443 (published as :443) # NDT7 cleartext: :8080 (published as :80) # Prometheus: :9990 docker run --network=bridge \ --publish 443:4443 \ --publish 80:8080 \ --volume "$(pwd)/certs:/certs:ro" \ --volume "$(pwd)/datadir:/datadir" \ --read-only \ --user "$(id -u):$(id -g)" \ --cap-drop=all \ ndt-server \ -cert /certs/cert.pem \ -key /certs/key.pem \ -datadir /datadir \ -ndt7_addr :4443 \ -ndt7_addr_cleartext :8080 # Server is now accepting NDT7 WebSocket upgrades on ports 443 (WSS) and 80 (WS) ``` ``` -------------------------------- ### M-Lab Server Discovery Request Source: https://github.com/m-lab/ndt-server/blob/main/spec/ndt7-protocol.md Example of an HTTP GET request from an NDT7 client to the locate.measurementlab.net service to discover an available server. ```http > GET /ndt7 HTTP/1.1 > Host: locate.measurementlab.net > User-Agent: MKEngine/0.1.0 > Accept: application/json > ``` -------------------------------- ### Setup NDT7 Handler and Register Endpoints Source: https://context7.com/m-lab/ndt-server/llms.txt Configure the central handler struct with necessary metadata and storage paths. Register its Download and Upload methods with an http.ServeMux to expose NDT7 WebSocket endpoints. ```go package main import ( "log" "net/http" "github.com/m-lab/ndt-server/metadata" "github.com/m-lab/ndt-server/ndt7/handler" "github.com/m-lab/ndt-server/ndt7/spec" "github.com/m-lab/tcp-info/eventsocket" ) func main() { ndt7Handler := &handler.Handler{ DataDir: "/var/spool/ndt", SecurePort: ":4443", InsecurePort: ":8080", ServerMetadata: []metadata.NameValue{ {Name: "deployment", Value: "stable"}, {Name: "region", Value: "us-east"}, }, CompressResults: true, Events: eventsocket.NullServer(), // or eventsocket.New("/run/ndt/events.sock") } mux := http.NewServeMux() mux.Handle("/", http.FileServer(http.Dir("html"))) mux.Handle(spec.DownloadURLPath, http.HandlerFunc(ndt7Handler.Download)) // /ndt/v7/download mux.Handle(spec.UploadURLPath, http.HandlerFunc(ndt7Handler.Upload)) // /ndt/v7/upload server := &http.Server{ Addr: ":8080", Handler: mux, } log.Fatal(server.ListenAndServe()) // NDT7 download: ws://localhost:8080/ndt/v7/download // NDT7 upload: ws://localhost:8080/ndt/v7/upload } ``` -------------------------------- ### GET /ndt/v7/download Source: https://context7.com/m-lab/ndt-server/llms.txt Initiates the NDT7 download subtest by upgrading to WebSocket. The server sends binary data and JSON measurement messages. The client should not send binary messages. ```APIDOC ## GET /ndt/v7/download — Download subtest Upgrades to WebSocket. The server sends binary bulk data messages and interspersed JSON measurement messages to the client for up to 10 seconds. The client must not send binary messages. Requires `Sec-WebSocket-Protocol: net.measurementlab.ndt.v7`. ### Method GET ### Endpoint /ndt/v7/download ### Request Example ```bash # Initiate NDT7 download test via WebSocket upgrade (cleartext) curl --include \ --no-buffer \ --header "Connection: Upgrade" \ --header "Upgrade: websocket" \ --header "Host: localhost" \ --header "Sec-WebSocket-Version: 13" \ --header "Sec-WebSocket-Key: $(openssl rand -base64 16)" \ --header "Sec-WebSocket-Protocol: net.measurementlab.ndt.v7" \ "http://localhost/ndt/v7/download" ``` ### Response #### Success Response (101 Switching Protocols) - **Sec-WebSocket-Protocol**: net.measurementlab.ndt.v7 After upgrade, server streams: - BinaryMessage (random data, scaled 8KB -> up to 1MB) - BinaryMessage - TextMessage (JSON measurement, ~every 250ms) - ... #### Response Example ``` HTTP/1.1 101 Switching Protocols Sec-WebSocket-Protocol: net.measurementlab.ndt.v7 ``` ``` -------------------------------- ### NDT7 WebSocket Upgrade Request Source: https://github.com/m-lab/ndt-server/blob/main/spec/ndt7-protocol.md This is an example of an HTTP request a client sends to initiate an NDT7 test. Ensure the 'Sec-WebSocket-Protocol' header includes 'net.measurementlab.ndt.v7'. ```http GET /ndt/v7/download HTTP/1.1 Host: localhost Connection: Upgrade Sec-WebSocket-Key: DOdm+5/Cm3WwvhfcAlhJoQ== Sec-WebSocket-Version: 13 Sec-WebSocket-Protocol: net.measurementlab.ndt.v7 Upgrade: websocket User-Agent: ooniprobe/3.0.0 ndt7-client-go/0.1.0 ``` -------------------------------- ### Measurement JSON Example Source: https://github.com/m-lab/ndt-server/blob/main/spec/data-format.md A single measurement object, containing fields like ElapsedTime and NumBytes. The 'ConnectionInfo' object is optional and may be omitted by the server. ```json { "AppInfo": { "ElapsedTime": 1234, "NumBytes": 1234 } } ``` -------------------------------- ### NDT7 Server Measurement Message Example Source: https://context7.com/m-lab/ndt-server/llms.txt This JSON structure represents a measurement message sent by the server during NDT7 download or upload subtests. It contains application and TCP-level information. ```json // Example server measurement message (sent as WebSocket TextMessage): { "AppInfo": { "ElapsedTime": 2500000, "NumBytes": 31457280 }, "ConnectionInfo": { "Client": "1.2.3.4:45678", "Server": "[::1]:443", "UUID": "ndt-abc123_1563200740_000000000001A2B3", "StartTime": "2024-03-15T10:30:00.123456789Z" }, "Origin": "server", "Test": "download", "TCPInfo": { "BusyTime": 2480000, "BytesAcked": 31522765, "BytesReceived": 0, "BytesSent": 31980032, "BytesRetrans": 4096, "ElapsedTime": 2500000, "MinRTT": 1200, "RTT": 1450, "RTTVar": 120, "RWndLimited": 0, "SndBufLimited": 15000 } } // ElapsedTime / RTT fields are in microseconds // Compute download speed: 8 * BytesAcked / ElapsedTime = bytes/μs = Mbps // Retransmission rate: BytesRetrans / BytesSent ≈ packet loss proxy ``` -------------------------------- ### NDT7 WebSocket Upgrade Response Source: https://github.com/m-lab/ndt-server/blob/main/spec/ndt7-protocol.md This is an example of an HTTP response a server sends to acknowledge and complete the NDT7 WebSocket handshake. The 'Sec-WebSocket-Protocol' header must match the client's request. ```http HTTP/1.1 101 Switching Protocols Sec-WebSocket-Protocol: net.measurementlab.ndt.v7 Sec-WebSocket-Accept: Nhz+x95YebD6Uvd4nqPC2fomoUQ= Upgrade: websocket Connection: Upgrade ``` -------------------------------- ### Prepare Runtime Environment (Linux) Source: https://github.com/m-lab/ndt-server/blob/main/README.md Creates necessary directories for certificates and data storage before running the NDT server. ```bash install -d certs datadir ``` -------------------------------- ### Initiate NDT7 Download Test via WebSocket Source: https://context7.com/m-lab/ndt-server/llms.txt This command initiates an NDT7 download test by performing a WebSocket upgrade request to the server's cleartext endpoint. It includes necessary headers for the upgrade and specifies the NDT7 protocol. The expected response is an HTTP 101 Switching Protocols, after which the server will stream measurement data. ```bash # Initiate NDT7 download test via WebSocket upgrade (cleartext) curl --include \ --no-buffer \ --header "Connection: Upgrade" \ --header "Upgrade: websocket" \ --header "Host: localhost" \ --header "Sec-WebSocket-Version: 13" \ --header "Sec-WebSocket-Key: $(openssl rand -base64 16)" \ --header "Sec-WebSocket-Protocol: net.measurementlab.ndt.v7" \ "http://localhost/ndt/v7/download" # Expected: HTTP/1.1 101 Switching Protocols # Sec-WebSocket-Protocol: net.measurementlab.ndt.v7 # # After upgrade, server streams: # BinaryMessage (random data, scaled 8KB -> up to 1MB) # BinaryMessage # TextMessage (JSON measurement, ~every 250ms) # ... ``` -------------------------------- ### Run NDT Server with Docker (Linux) Source: https://context7.com/m-lab/ndt-server/llms.txt This snippet demonstrates how to build and run the NDT server using Docker on Linux. It includes steps for preparing directories, generating TLS certificates, enabling TCP BBR, building the Docker image, and running the container with port mappings and volume mounts. Ensure TCP BBR is enabled for improved NDT7 accuracy. ```bash # Prepare runtime directories install -d certs datadir # Generate self-signed TLS certificates for local testing ./gen_local_test_certs.bash # Enable TCP BBR congestion control (improves ndt7 accuracy) sudo modprobe tcp_bbr # Build Docker image docker build . -t ndt-server # Run the server # NDT7 TLS: :4443 (published as :443) # NDT7 cleartext: :8080 (published as :80) # Prometheus: :9990 docker run --network=bridge \ --publish 443:4443 \ --publish 80:8080 \ --volume "$(pwd)/certs:/certs:ro" \ --volume "$(pwd)/datadir:/datadir" \ --read-only \ --user "$(id -u):$(id -g)" \ --cap-drop=all \ ndt-server \ -cert /certs/cert.pem \ -key /certs/key.pem \ -datadir /datadir \ -ndt7_addr :4443 \ -ndt7_addr_cleartext :8080 # Server is now accepting NDT7 WebSocket upgrades on ports 443 (WSS) and 80 (WS) ``` -------------------------------- ### Run Basic NDT Server Source: https://github.com/m-lab/ndt-server/blob/main/fullstack/README.md Use this command to run a basic NDT server that speaks the unencrypted NDT5 protocol. Data will be saved to an in-container directory. ```bash $ docker run --net=host measurementlab/ndt ``` -------------------------------- ### Run NDT7 Server with TLS Source: https://github.com/m-lab/ndt-server/blob/main/fullstack/README.md To run NDT7 tests or NDT5 tests over TLS, you need a private key and TLS certificate. Mount the certificate directory and provide the key and cert paths to the server. ```bash $ docker run -v /etc/certs:/certs --net=host measurementlab/ndt \ --key=/certs/key.pem --cert=/certs/cert.pem ``` -------------------------------- ### Run NDT7 Speed Test with Callbacks Source: https://github.com/m-lab/ndt-server/blob/main/html/ndt7.html Initiates an NDT7 speed test. Configure test parameters like worker files, metadata, server, and protocol. Use callbacks to handle server selection, measurement updates, completion, and errors. ```javascript ndt7.test( { userAcceptedDataPolicy: true, downloadworkerfile: "ndt7-download-worker.js", uploadworkerfile: "ndt7-upload-worker.js", metadata: { client_name: 'ndt-server-example', }, server: location.host, protocol: location.protocol === 'https:' ? 'wss' : 'ws', }, { serverChosen: function (server) { console.log('Testing to:', { machine: server.machine, locations: server.location }); document.getElementById('server').innerHTML = server.machine + ' (' + server.location.city + ')'; }, downloadMeasurement: function (data) { if (data.Source === 'client') { document.getElementById('download').innerHTML = 'Download: ' + data.Data.MeanClientMbps.toFixed(2) + ' Mb/s'; } }, downloadComplete: function (data) { // (bytes/second) * (bits/byte) / (megabits/bit) = Mbps const serverBw = data.LastServerMeasurement.BBRInfo.BW * 8 / 1000000; const clientGoodput = data.LastClientMeasurement.MeanClientMbps; console.log( `Download test is complete: Instantaneous server bottleneck bandwidth estimate: ${serverBw} Mbps Mean client goodput: ${clientGoodput} Mbps` ); document.getElementById('download').innerHTML = clientGoodput.toFixed(2) + ' Mb/s'; }, uploadMeasurement: function (data) { if (data.Source === 'server') { document.getElementById('upload').innerHTML = (data.Data.TCPInfo.BytesReceived / data.Data.TCPInfo.ElapsedTime * 8).toFixed(2) + ' Mb/s';; } }, uploadComplete: function (data) { const bytesReceived = data.LastServerMeasurement.TCPInfo.BytesReceived; const elapsed = data.LastServerMeasurement.TCPInfo.ElapsedTime; // bytes * bits/byte / microseconds = Mbps const throughput = bytesReceived * 8 / elapsed; console.log( `Upload test completed in ${(elapsed / 1000000).toFixed(2)}s Mean server throughput: ${throughput} Mbps` ); }, error: function (err) { console.log('Error while running the test:', err.message); }, } ).then((exitcode) => { console.log("ndt7 test completed with exit code:", exitcode) }); ``` -------------------------------- ### NDT Server Command-Line Flags Source: https://context7.com/m-lab/ndt-server/llms.txt Lists all available command-line flags for configuring the NDT server, including listening addresses, TLS settings, data storage, and token authentication. Defaults are shown for each flag. ```bash # All available flags with defaults: ndt-server \ -ndt7_addr :443 \ -ndt7_addr_cleartext :80 \ -ndt5_addr :3001 \ -ndt5_ws_addr 127.0.0.1:3002\ -ndt5_wss_addr :3010 \ -health_addr 127.0.0.1:8000\ -cert "" \ -key "" \ -tls.version "" \ -datadir /var/spool/ndt\ -htmldir html \ -compress-results true \ -label deployment=stable \ -ndt5.token.required false \ -ndt7.token.required false \ -token.verify-key /path/to/pub.pem \ -token.machine /path/to/machine \ -autocert.enabled false \ -autocert.hostname "" \ -autocert.dir autocert ``` -------------------------------- ### Run NDT Server Docker Container (Linux) Source: https://github.com/m-lab/ndt-server/blob/main/README.md Runs the ndt-server Docker container, mapping ports, volumes, and configuring server parameters. Ensure BBR is enabled for optimal performance. ```bash docker run --network=bridge \ --publish 443:4443 \ --publish 80:8080 \ --volume `pwd`/certs:/certs:ro \ --volume `pwd`/datadir:/datadir \ --read-only \ --user `id -u`:`id -g` \ --cap-drop=all \ ndt-server \ -cert /certs/cert.pem \ -key /certs/key.pem \ -datadir /datadir \ -ndt7_addr :4443 \ -ndt7_addr_cleartext :8080 ``` -------------------------------- ### Wrap HTTP Handlers with Access Logging Source: https://context7.com/m-lab/ndt-server/llms.txt Use `logging.MakeAccessLogHandler` to wrap an `http.Handler` for Combined Log Format access logging to stdout. This is useful for containerized environments. ```go package main import ( "net/http" "github.com/m-lab/ndt-server/logging" "github.com/m-lab/ndt-server/ndt7/listener" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/ndt/v7/download", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) }) // Wrap mux with access logging middleware loggedHandler := logging.MakeAccessLogHandler(mux) server := &http.Server{Addr: ":8080", Handler: loggedHandler} listener.ListenAndServeAsync(server) // stdout: 203.0.113.10 - - [15/Mar/2024:10:30:05 +0000] "GET /ndt/v7/download HTTP/1.1" 200 0 select {} } ``` -------------------------------- ### Generate Local Test Certificates (Docker Compose) Source: https://github.com/m-lab/ndt-server/blob/main/README.md Generates local test certificates using Docker Compose, suitable for Windows and MacOS environments. ```bash docker-compose run ndt-server ./gen_local_test_certs.bash ``` -------------------------------- ### Build Docker Container for NDT Server Source: https://github.com/m-lab/ndt-server/blob/main/README.md Builds the Docker image for the ndt-server from the current directory. ```bash docker build . -t ndt-server ``` -------------------------------- ### Non-interactive Client Scheduling Algorithm in Go Source: https://github.com/m-lab/ndt-server/blob/main/spec/ndt7-protocol.md Implements the recommended algorithm for non-interactive clients to schedule tests with randomized sleep times, adhering to specified minimum and maximum durations. Ensures the random number generator is seeded. ```Go import ( "math/rand" "sync" "time" ) var once sync.Once func sleepTime() time.Duration { once.Do(func() { rand.Seed(time.Now().UTC().UnixNano()) }) t := rand.ExpFloat64() * 21600 if t < 2160 { t = 2160 } else if t > 54000 { t = 54000 } return time.Duration(t * float64(time.Second)) } func mainLoop() { for { tryPerformanceTest() time.Sleep(sleepTime()) } } ``` -------------------------------- ### Exponential Backoff for Server Location Retries in Go Source: https://github.com/m-lab/ndt-server/blob/main/spec/ndt7-protocol.md Handles retries when locating a server fails due to no capacity, implementing an exponential backoff strategy with increasing averages and a fixed standard deviation. Assumes the RNG is already seeded. ```Go func tryPerformanceTest() { for mean := 60.0; mean <= 960.0; mean *= 2.0 { fqdn, err := locatesvc.LocateServer() if err != nil && err != locatesvc.ErrNoContent { return } if err != nil { // Note: RNG already seeded as shown above stdev := 0.05 * mean seconds := rand.NormFloat64() * stdev + mean time.Sleep(time.Duration(seconds * float64(time.Second))) continue } runWithServer(fqdn) return } } ``` -------------------------------- ### Server configuration flags Source: https://context7.com/m-lab/ndt-server/llms.txt Lists and describes the available command-line flags for configuring the NDT server, including listening addresses, TLS settings, data storage, and authentication. ```APIDOC ## Server configuration flags The binary accepts flags to configure all listening addresses, TLS, data storage, and optional access token enforcement. ```bash # All available flags with defaults: ndt-server \ -ndt7_addr :443 \ -ndt7_addr_cleartext :80 \ -ndt5_addr :3001 \ -ndt5_ws_addr 127.0.0.1:3002\ -ndt5_wss_addr :3010 \ -health_addr 127.0.0.1:8000\ -cert "" \ -key "" \ -tls.version "" \ -datadir /var/spool/ndt\ -htmldir html \ -compress-results true \ -label deployment=stable \ -ndt5.token.required false \ -ndt7.token.required false \ -token.verify-key /path/to/pub.pem \ -token.machine /path/to/machine \ -autocert.enabled false \ -autocert.hostname "" \ -autocert.dir autocert ``` ``` -------------------------------- ### NDT5 Control Channel Handler (Legacy) Source: https://context7.com/m-lab/ndt-server/llms.txt The `ndt5.HandleControlChannel` function manages the NDT5 test negotiation flow, including login, C2S upload, S2C download, and metadata exchange. It is intended for legacy clients only and not recommended for new integrations. ```go // From ndt5/ndt5.go — internal usage pattern (simplified): // HandleControlChannel is called by the plain/ws/wss servers for each new connection. // // conn — wraps the underlying TCP/WS/WSS connection // s — provides per-connection server context (data dir, metadata, port info) // isMon — "true"/"false" label indicating monitoring vs. normal client // Example: the plain server accepts connections and dispatches them: // // for { // conn, _ := listener.Accept() // go ndt5.HandleControlChannel(conn, server, "false") // } // // A successful NDT5 test session flow: // Client → SrvQueue (queue position = 0) // Server → MsgLogin "v5.0-NDTinGO" // Server → MsgLogin "" // e.g. "2 4 32" = C2S + S2C + META // [C2S subtest: client uploads for 10s, server measures throughput] // [S2C subtest: server downloads to client for 10s] // [META subtest: client sends key=value metadata pairs] // Server → MsgResults "You uploaded at 12345.0 and downloaded at 98765.0" // Server → MsgLogout // NDT5 Prometheus metrics emitted per test: // ndt5_control_total{protocol="WSS", result="okay"} += 1 // ndt5_client_test_results_total{protocol="WSS", direction="s2c", result="okay-with-rate"} += 1 // test_rate_mbps_bucket{protocol="WSS", direction="s2c", isMon="false", le="..."} ``` -------------------------------- ### Initiate NDT7 Upload Test (TLS) Source: https://context7.com/m-lab/ndt-server/llms.txt Use this cURL command to initiate an NDT7 upload test over TLS. Ensure the Sec-WebSocket-Protocol header is set correctly for NDT7. ```bash # Initiate NDT7 upload test (TLS) curl --include \ --no-buffer \ --insecure \ --header "Connection: Upgrade" \ --header "Upgrade: websocket" \ --header "Host: localhost" \ --header "Sec-WebSocket-Version: 13" \ --header "Sec-WebSocket-Key: $(openssl rand -base64 16)" \ --header "Sec-WebSocket-Protocol: net.measurementlab.ndt.v7" \ "https://localhost/ndt/v7/upload" # Expected: HTTP/1.1 101 Switching Protocols # # After upgrade: # Client sends BinaryMessage (bulk upload data) # Server sends TextMessage (JSON measurement, ~every 250ms) # ... # Client sends CloseMessage when done ``` -------------------------------- ### NDT7 Download - With Measurement Messages Source: https://github.com/m-lab/ndt-server/blob/main/spec/ndt7-protocol.md Shows an NDT7 download test where the server sends measurement messages, providing client-side elapsed time. ```text > GET /ndt/v7/download Upgrade: websocket < 101 Switching Protocols < BinaryMessage < BinaryMessage < TextMessage clientElapsedTime=0.30 s < BinaryMessage < BinaryMessage < TextMessage clientElapsedTime=0.55 s < BinaryMessage ... < CloseMessage > CloseMessage ``` -------------------------------- ### Request Nearby NDT7 Server via Locate API Source: https://context7.com/m-lab/ndt-server/llms.txt Clients should query the `locate.measurementlab.net` service to find an appropriate ndt7 server. The response provides the FQDN for the server. ```bash # Request a nearby ndt7 server curl -H "User-Agent: myapp/1.0" \ "https://locate.measurementlab.net/ndt7" # 200 response — server available: # { "fqdn": "ndt-iupui-mlab2-tun01.measurement-lab.org" } # 204 response — no capacity, retry with backoff # Use returned FQDN to run download test: FQDN="ndt-iupui-mlab2-tun01.measurement-lab.org" # Connect to: wss://${FQDN}/ndt/v7/download # with header: Sec-WebSocket-Protocol: net.measurementlab.ndt.v7 ``` -------------------------------- ### NDT7 Download Subtest Source: https://context7.com/m-lab/ndt-server/llms.txt Initiates the NDT7 download subtest. The client can optionally pass client metadata as query parameters. The server can be configured for early exit after a certain amount of data is transferred. ```APIDOC ## GET /ndt/v7/download ### Description Initiates the NDT7 download subtest. The client can optionally pass client metadata as query parameters. The server can be configured for early exit after a certain amount of data is transferred. ### Method GET ### Endpoint /ndt/v7/download ### Query Parameters - **ClientLibraryName** (string) - Optional - Name of the client library. - **ClientLibraryVersion** (string) - Optional - Version of the client library. - **early_exit** (integer) - Optional - Early exit after transferring this many megabytes. ``` -------------------------------- ### M-Lab Server Discovery - Successful Response Source: https://github.com/m-lab/ndt-server/blob/main/spec/ndt7-protocol.md A successful HTTP response from locate.measurementlab.net, providing the FQDN of an NDT7-enabled server. ```http < HTTP/1.1 200 < Cache-Control: no-cache < Access-Control-Allow-Origin: * < Content-Type: application/json < Date: Thu, 02 May 2019 13:46:32 GMT < Server: Google Frontend < Content-Length: 622 < { "fqdn": "ndt-iupui-mlab2-tun01.measurement-lab.org" } ``` -------------------------------- ### Generate Local Test Certificates (Bash) Source: https://github.com/m-lab/ndt-server/blob/main/README.md Generates self-signed SSL certificates required for local testing of the NDT server using OpenSSL. ```bash ./gen_local_test_certs.bash ``` -------------------------------- ### NDT7 Upload - With Server Measurement Messages Source: https://github.com/m-lab/ndt-server/blob/main/spec/ndt7-protocol.md Illustrates an NDT7 upload test where the server sends measurement messages to the client. ```text > GET /ndt/v7/upload Upgrade: websocket < 101 Switching Protocols > BinaryMessage > BinaryMessage > BinaryMessage < TextMessage clientElapsedTime=0.25 s > BinaryMessage > BinaryMessage < TextMessage clientElapsedTime=0.51 s > BinaryMessage ... > CloseMessage < CloseMessage ``` -------------------------------- ### Write NDT7 Archival Result Files Source: https://context7.com/m-lab/ndt-server/llms.txt Use `results.NewFile` to create uniquely named result files, optionally compressed, within a date-partitioned directory structure. `WriteResult` serializes Go structs into JSON within these files. ```go package main import ( "log" "time" "github.com/m-lab/ndt-server/ndt7/results" "github.com/m-lab/ndt-server/ndt7/spec" "github.com/m-lab/ndt-server/data" ) func saveResult(uuid, dataDir string) { // Creates: /ndt7/2024/03/15/ndt7-download-20240315T103005.123456789Z..json.gz fp, err := results.NewFile(uuid, dataDir, spec.SubtestDownload, true /* compress */) if err != nil { log.Printf("NewFile error: %v", err) return } defer fp.Close() result := &data.NDT7Result{ GitShortCommit: "abc1234", Version: "v0.20.0", ClientIP: "203.0.113.10", ClientPort: 51000, ServerIP: "192.0.2.1", ServerPort: 443, StartTime: time.Now().UTC(), EndTime: time.Now().UTC().Add(10 * time.Second), } if err := fp.WriteResult(result); err != nil { log.Printf("WriteResult error: %v", err) return } log.Printf("Wrote result for UUID %s to %s", uuid, fp.UUID) } ``` -------------------------------- ### Non-interactive Client Scheduling Logic Source: https://context7.com/m-lab/ndt-server/llms.txt This Go code snippet implements a randomized sleep duration for client scheduling, ensuring tests are distributed over time. It uses `rand.ExpFloat64` for exponential distribution and enforces minimum and maximum sleep times. ```go import ( "math/rand" "sync" "time" ) var once sync.Once func sleepTime() time.Duration { once.Do({ rand.Seed(time.Now().UTC().UnixNano()) }) t := rand.ExpFloat64() * 21600 // mean = 6 hours if t < 2160 { t = 2160 } // min = 36 min if t > 54000 { t = 54000 } // max = 15 hours return time.Duration(t * float64(time.Second)) } func mainLoop() { for { runNDT7Test() // connect to locate API, then run download + upload time.Sleep(sleepTime()) } } ``` -------------------------------- ### Enable BBR Congestion Control Source: https://github.com/m-lab/ndt-server/blob/main/README.md Enables the BBR congestion control algorithm, which is recommended for better performance with NDT7. ```bash sudo modprobe tcp_bbr ``` -------------------------------- ### M-Lab Server Discovery - No Capacity Response Source: https://github.com/m-lab/ndt-server/blob/main/spec/ndt7-protocol.md An HTTP response indicating no available capacity from locate.measurementlab.net, signaled by a 204 status code. ```http < HTTP/1.1 204 No Content < Cache-Control: no-cache < Access-Control-Allow-Origin: * < Content-Type: application/json < Date: Thu, 02 May 2019 13:46:32 GMT < Server: Google Frontend < Content-Length: 0 < ``` -------------------------------- ### Adapting Binary Message Size in Go Source: https://github.com/m-lab/ndt-server/blob/main/spec/ndt7-protocol.md Dynamically adjusts the size of outgoing messages to reduce overhead, particularly for browser-based clients. Doubles the message size if it's smaller than a fixed percentage of queued bytes or a maximum threshold. ```Go var total int64 msg.Resize(1 << 13) for { if err := conn.Send(msg); err != nil { return err } total += msg.Size() if msg.Size() >= (1 << 24) || msg.Size() >= (total / 16) { continue } msg.Resize(msg.Size() * 2) } ``` -------------------------------- ### Query NDT7 Prometheus Metrics Source: https://context7.com/m-lab/ndt-server/llms.txt This command queries the Prometheus metrics endpoint for NDT7-related metrics. The endpoint is typically exposed on port 9990. ```bash curl http://localhost:9990/metrics | grep ndt7 # Key NDT7 metrics: # ndt7_client_connections_total{direction="download",status="result"} 1234 ``` -------------------------------- ### NDT7 Download - No Text Messages Source: https://github.com/m-lab/ndt-server/blob/main/spec/ndt7-protocol.md Illustrates a basic NDT7 download test where no text messages are exchanged between client and server. ```text > GET /ndt/v7/download Upgrade: websocket < 101 Switching Protocols < BinaryMessage < BinaryMessage < BinaryMessage < BinaryMessage ... < CloseMessage > CloseMessage ``` -------------------------------- ### Health Check Endpoint Source: https://context7.com/m-lab/ndt-server/llms.txt Liveness probe endpoint. Returns 200 OK when the server is operating normally and 500 Internal Server Error when graceful shutdown is in progress. ```APIDOC ## GET /health ### Description Liveness probe. Returns `200 OK` when the server is operating normally. Returns `500 Internal Server Error` when the server has received its first `SIGTERM` and entered lame duck mode (i.e., graceful shutdown is in progress). Used by load balancers and orchestrators to drain traffic before shutdown. ### Method GET ### Endpoint /health ### Response #### Success Response (200 OK) Server is operating normally. #### Error Response (500 Internal Server Error) Graceful shutdown is in progress. ``` -------------------------------- ### NDT7 Upload Subtest Source: https://context7.com/m-lab/ndt-server/llms.txt Upgrades the connection to WebSocket for the NDT7 upload subtest. The client sends binary bulk data, and the server measures throughput using TCPInfo, sending JSON measurement messages back. ```APIDOC ## GET /ndt/v7/upload ### Description Upgrades to WebSocket. The client sends binary bulk data; the server receives, measures throughput using `TCPInfo`, and sends JSON measurement messages back. Requires `Sec-WebSocket-Protocol: net.measurementlab.ndt.v7`. ### Method GET ### Endpoint /ndt/v7/upload ### Request Headers - **Connection**: Upgrade - **Upgrade**: websocket - **Host**: [hostname] - **Sec-WebSocket-Version**: 13 - **Sec-WebSocket-Key**: [randomly generated key] - **Sec-WebSocket-Protocol**: net.measurementlab.ndt.v7 ### Response #### Success Response (101 Switching Protocols) Indicates a successful upgrade to the WebSocket protocol. ### After Upgrade - Client sends BinaryMessage (bulk upload data). - Server sends TextMessage (JSON measurement, ~every 250ms). - Client sends CloseMessage when done. ``` -------------------------------- ### NDT7 Result File Naming Pattern Source: https://context7.com/m-lab/ndt-server/llms.txt This pattern illustrates how NDT7 result files are named, including the subtest type, timestamp, and a unique identifier. Files may be gzipped. ```bash # File naming pattern: ndt7-download-20240315T103005.123456789Z..json.gz ndt7-upload-20240315T103015.456789012Z..json.gz ``` -------------------------------- ### Prometheus Metrics Endpoint Source: https://context7.com/m-lab/ndt-server/llms.txt Exposes Prometheus metrics for monitoring NDT7 and NDT5 test traffic, connection rates, errors, and throughput distributions. ```APIDOC ## GET /metrics ### Description Exposes Prometheus metrics for monitoring NDT7 and NDT5 test traffic, connection rates, errors, and throughput distributions. ### Method GET ### Endpoint /metrics ### Notes - Exposed on port `9990` by default (configurable via `PROMETHEUSX_LISTEN_ADDRESS`). ``` -------------------------------- ### Persist NDT Data Source: https://github.com/m-lab/ndt-server/blob/main/fullstack/README.md Mount an external directory into the container at `/var/spool/ndt` to save NDT server data outside the Docker container. ```bash $ docker run -v /path/to/host/data:/var/spool/ndt --net=host measurementlab/ndt ``` -------------------------------- ### NDT7 Upload - No Text Messages Source: https://github.com/m-lab/ndt-server/blob/main/spec/ndt7-protocol.md Illustrates a basic NDT7 upload test where no text messages are exchanged between client and server. ```text > GET /ndt/v7/upload Upgrade: websocket < 101 Switching Protocols > BinaryMessage > BinaryMessage > BinaryMessage > BinaryMessage ... > CloseMessage < CloseMessage ``` -------------------------------- ### NDT Server Health Check Endpoint Source: https://context7.com/m-lab/ndt-server/llms.txt The /health endpoint is used for liveness probes. It returns 200 OK during normal operation and 500 Internal Server Error when the server is shutting down (lame duck mode). ```bash # Normal operation curl -i http://localhost:8000/health # HTTP/1.1 200 OK # After first SIGTERM (lame duck mode active) curl -i http://localhost:8000/health # HTTP/1.1 500 Internal Server Error # Kubernetes liveness/readiness probe configuration: # livenessProbe: # httpGet: # path: /health # port: 8000 # initialDelaySeconds: 5 # periodSeconds: 10 ``` -------------------------------- ### ndt7 Filename Pattern Source: https://github.com/m-lab/ndt-server/blob/main/spec/data-format.md The filename pattern for ndt7 data files. Ensure files adhere to this format for proper identification and processing. ```text ndt7--T.Z..json.gz ``` -------------------------------- ### Full NDT7 Result JSON Structure Source: https://context7.com/m-lab/ndt-server/llms.txt This is the complete JSON structure for an NDT7 result file written to disk. It includes metadata, test details, and measurements from both client and server. ```json // Full NDT7Result structure written to disk: { "GitShortCommit": "773d318", "Version": "v0.20.0", "ClientIP": "203.0.113.42", "ClientPort": 51234, "ServerIP": "192.0.2.1", "ServerPort": 443, "StartTime": "2024-03-15T10:30:05.987748459Z", "EndTime": "2024-03-15T10:30:16.008714743Z", "Download": { "UUID": "ndt-server01_1710498605_000000000001F4A2", "StartTime": "2024-03-15T10:30:05.987853779Z", "EndTime": "2024-03-15T10:30:15.988677965Z", "ServerMeasurements": [ { "AppInfo": { "ElapsedTime": 250000, "NumBytes": 2097152 }, "TCPInfo": { "BytesAcked": 2101248, "ElapsedTime": 250000, "RTT": 1500 } } ], "ClientMeasurements": [], "ClientMetadata": [ { "Name": "ClientLibraryName", "Value": "ndt7-js" }, { "Name": "ClientLibraryVersion", "Value": "0.2.0" } ], "ServerMetadata": [ { "Name": "deployment", "Value": "stable" }, { "Name": "machine-type", "Value": "physical" } ] } } ``` -------------------------------- ### ndt7 Result JSON Structure Source: https://github.com/m-lab/ndt-server/blob/main/spec/data-format.md The complete record of an ndt7 test, including client/server details, timestamps, and upload/download summaries. This is the primary JSON structure written to disk. ```json { "GitShortCommit": "773d318", "Version": "v0.9.1-20-g773d318", "ClientIP": "::1", "ClientPort": 40910, "ServerIP": "::1", "ServerPort": 443, "StartTime": "2019-07-16T15:26:05.987748459-04:00", "EndTime": "2019-07-16T15:26:16.008714743-04:00", "Upload": { "StartTime": "2019-07-16T15:26:05.987853779-04:00", "EndTime": "2019-07-16T15:26:16.008677965-04:00", "UUID": "soltesz99.nyc.corp.google.com_1563200740_unsafe_00000000000157C6", "ClientMeasurements": [ ], "ClientMetadata": [ ], "ServerMetadata": [ ], "ServerMeasurements": [ ] } } ```