// -=-=- StartHere -=-=- // // # Chapter II: establishing TCP connections // // In this chapter we explain how to measure establishing TCP connections. // // We will first write a simple `main.go` file that shows how to use // this functionality. Then, we will show some runs of this file, and // we will comment the output that we see. // // (This file is auto-generated. Do not edit it directly! To apply // changes you need to modify `./internal/tutorial/measurex/chapter02/main.go`.) // // ## main.go // // We declare the package and import useful packages. The most // important package we're importing here is, of course, `internal/measurex`. // // ```Go package main import ( "context" "encoding/json" "flag" "fmt" "time" "github.com/ooni/probe-cli/v3/internal/measurex" "github.com/ooni/probe-cli/v3/internal/runtimex" ) func main() { // ``` // ### Setup // // This first part of `main.go` is really similar to the previous // chapter, so there is not much new to say here. // // ```Go address := flag.String("address", "8.8.4.4:443", "remote endpoint address") timeout := flag.Duration("timeout", 60*time.Second, "timeout to use") flag.Parse() ctx, cancel := context.WithTimeout(context.Background(), *timeout) defer cancel() // ``` // // ### Creating a Measurer // // We create a `Measurer` like we did in the previous chapter. // // ```Go mx := measurex.NewMeasurerWithDefaultSettings() // ``` // // ### Establishing a TCP connection // // We then call `TCPConnect`, which establishes a connection // and returns the corresponding measurement. // // The arguments are the context (for timeouts), and the address // of the endpoint to which we want to connect. (Here and in // most of this tutorial with "endpoint" we mean an IP address // and a port, serialized as "ADDRESS:PORT", where the // address is quoted with "[" and "]" if IPv6, e.g., `[::1]:53`.) // // ```Go m := mx.TCPConnect(ctx, *address) // ``` // // ### Printing the measurement // // The rest of the main function is just like in the previous chapter. // // ```Go data, err := json.Marshal(m) runtimex.PanicOnError(err, "json.Marshal failed") fmt.Printf("%s\n", string(data)) } // ``` // // ## Running the example program // // Let us run the program with default arguments first. You can do // this operation by running: // // ```bash // go run -race ./internal/tutorial/measurex/chapter02 | jq // ``` // // Here is the JSON we obtain in output: // // ```JavaScript // { // // These two fields identify the endpoint // "network": "tcp", // "address": "8.8.4.4:443", // // // This block contains the results of the connect syscall // // using the df-008-netevents data format. // "connect": [ // { // "address": "8.8.4.4:443", // "failure": null, // "operation": "connect", // "proto": "tcp", // "t": 0.026879041, // "started": 8.8625e-05, // "oddity": "" // } // ] // } // ``` // // This is what it says: // // - we are connecting a "tcp" socket; // // - the destination endpoint address is "8.8.4.4:443"; // // - connect terminated ~0.027 seconds into the program's life; // // - the operation succeeded (`failure` is `nil`). // // Let us now see if we can provoke some errors and timeouts. // // ### Measurement with connection refused // // Let us start with an IP address where there's no listening socket: // // ```bash // go run -race ./internal/tutorial/measurex/chapter02 -address 127.0.0.1:1 | jq // ``` // // We get this JSON: // // ```JSON // { // "network": "tcp", // "address": "127.0.0.1:1", // "connect": [ // { // "address": "127.0.0.1:1", // "failure": "connection_refused", // "operation": "connect", // "proto": "tcp", // "t": 0.000372167, // "started": 8.4917e-05, // "oddity": "tcp.connect.refused" // } // ] // } // // ``` // // And here's an error telling us the connection was refused and // the oddity that classifies the error. // // ### Measurement with timeouts // // Let us now try to obtain a timeout: // // ```bash // go run -race ./internal/tutorial/measurex/chapter02 -address 8.8.4.4:1 | jq // ``` // // We get this JSON: // // ```JSON // { // "network": "tcp", // "address": "8.8.4.4:1", // "connect": [ // { // "address": "8.8.4.4:1", // "failure": "generic_timeout_error", // "operation": "connect", // "proto": "tcp", // "t": 10.005494583, // "started": 8.4833e-05, // "oddity": "tcp.connect.timeout" // } // ] // } // ``` // // So, we clearly see from the value of `t` that our 60 seconds // default timeout did not hit, because there is a lower watchdog // timeout (10 s). We also see again how the oddity is more // precise than just the error alone. // // Let us now use a very small timeout: // // ```bash // go run -race ./internal/tutorial/measurex/chapter02 -address 8.8.4.4:1 -timeout 100ms | jq // ``` // // To get this JSON: // // ```JSON // { // "network": "tcp", // "address": "8.8.4.4:1", // "connect": [ // { // "address": "8.8.4.4:1", // "failure": "generic_timeout_error", // "operation": "connect", // "proto": "tcp", // "t": 0.10148025, // "started": 0.000122375, // "oddity": "tcp.connect.timeout" // } // ] // } // ``` // // We see a timeout after ~0.1s. We enforce a reasonably small // timeout for connecting, equal to 10 s, because we want to // guarantee that measurements eventually terminate. Also, since // often censorship is implemented by timing out, we don't want // to spend to much time waiting for a timeout to expire. // // ## Conclusions // // We have seen how to measure the operation of connecting // to a specific TCP endpoint. // // -=-=- StopHere -=-=-