diff --git a/internal/measurexlite/conn.go b/internal/measurexlite/conn.go index 54beade59..ae107e88b 100644 --- a/internal/measurexlite/conn.go +++ b/internal/measurexlite/conn.go @@ -39,11 +39,29 @@ type connTrace struct { var _ net.Conn = &connTrace{} +type remoteAddrProvider interface { + RemoteAddr() net.Addr +} + +func safeRemoteAddrNetwork(rap remoteAddrProvider) (result string) { + if addr := rap.RemoteAddr(); addr != nil { + result = addr.Network() + } + return result +} + +func safeRemoteAddrString(rap remoteAddrProvider) (result string) { + if addr := rap.RemoteAddr(); addr != nil { + result = addr.String() + } + return result +} + // Read implements net.Conn.Read and saves network events. func (c *connTrace) Read(b []byte) (int, error) { // collect preliminary stats when the connection is surely active - network := c.RemoteAddr().Network() - addr := c.RemoteAddr().String() + network := safeRemoteAddrNetwork(c) + addr := safeRemoteAddrString(c) started := c.tx.TimeSince(c.tx.ZeroTime()) // perform the underlying network operation @@ -99,8 +117,8 @@ func (tx *Trace) CloneBytesReceivedMap() (out map[string]int64) { // Write implements net.Conn.Write and saves network events. func (c *connTrace) Write(b []byte) (int, error) { - network := c.RemoteAddr().Network() - addr := c.RemoteAddr().String() + network := safeRemoteAddrNetwork(c) + addr := safeRemoteAddrString(c) started := c.tx.TimeSince(c.tx.ZeroTime()) count, err := c.Conn.Write(b) diff --git a/internal/measurexlite/conn_test.go b/internal/measurexlite/conn_test.go index 563ce31db..d69f52780 100644 --- a/internal/measurexlite/conn_test.go +++ b/internal/measurexlite/conn_test.go @@ -12,6 +12,43 @@ import ( "github.com/ooni/probe-cli/v3/internal/testingx" ) +func TestRemoteAddrProvider(t *testing.T) { + t.Run("for nil address", func(t *testing.T) { + conn := &mocks.Conn{ + MockRemoteAddr: func() net.Addr { + return nil + }, + } + if safeRemoteAddrNetwork(conn) != "" { + t.Fatal("expected empty network") + } + if safeRemoteAddrString(conn) != "" { + t.Fatal("expected empty string") + } + }) + + t.Run("for common case", func(t *testing.T) { + conn := &mocks.Conn{ + MockRemoteAddr: func() net.Addr { + return &mocks.Addr{ + MockString: func() string { + return "1.1.1.1:443" + }, + MockNetwork: func() string { + return "tcp" + }, + } + }, + } + if safeRemoteAddrNetwork(conn) != "tcp" { + t.Fatal("unexpected network") + } + if safeRemoteAddrString(conn) != "1.1.1.1:443" { + t.Fatal("unexpected string") + } + }) +} + func TestMaybeClose(t *testing.T) { t.Run("with nil conn", func(t *testing.T) { var conn net.Conn = nil