Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(UnderlyingNetwork): add support for ListenTCP #1278

Merged
merged 2 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions internal/mocks/underlyingnetwork.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ type UnderlyingNetwork struct {

MockDialContext func(ctx context.Context, network, address string) (net.Conn, error)

MockListenTCP func(network string, addr *net.TCPAddr) (net.Listener, error)

MockListenUDP func(network string, addr *net.UDPAddr) (model.UDPLikeConn, error)

MockGetaddrinfoLookupANY func(ctx context.Context, domain string) ([]string, string, error)
Expand All @@ -38,6 +40,10 @@ func (un *UnderlyingNetwork) DialContext(ctx context.Context, network, address s
return un.MockDialContext(ctx, network, address)
}

func (un *UnderlyingNetwork) ListenTCP(network string, addr *net.TCPAddr) (net.Listener, error) {
return un.MockListenTCP(network, addr)
}

func (un *UnderlyingNetwork) ListenUDP(network string, addr *net.UDPAddr) (model.UDPLikeConn, error) {
return un.MockListenUDP(network, addr)
}
Expand Down
16 changes: 16 additions & 0 deletions internal/mocks/underlyingnetwork_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@ func TestUnderlyingNetwork(t *testing.T) {
}
})

t.Run("ListenTCP", func(t *testing.T) {
expect := errors.New("mocked error")
un := &UnderlyingNetwork{
MockListenTCP: func(network string, addr *net.TCPAddr) (net.Listener, error) {
return nil, expect
},
}
listener, err := un.ListenTCP("tcp", &net.TCPAddr{})
if !errors.Is(err, expect) {
t.Fatal("unexpected err", err)
}
if listener != nil {
t.Fatal("expected nil listener")
}
})

t.Run("ListenUDP", func(t *testing.T) {
expect := errors.New("mocked error")
un := &UnderlyingNetwork{
Expand Down
12 changes: 12 additions & 0 deletions internal/model/measurement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,20 @@ import (
"errors"
"fmt"
"testing"
"time"
)

func TestMeasurementFormatTimeNowUTC(t *testing.T) {
t.Run("produces a string using the correct date format", func(t *testing.T) {
out := MeasurementFormatTimeNowUTC()
result, err := time.Parse(MeasurementDateFormat, out)
if err != nil {
t.Fatal(err)
}
_ = result
})
}

func TestMeasurementTargetMarshalJSON(t *testing.T) {
var mt MeasurementTarget
data, err := json.Marshal(mt)
Expand Down
3 changes: 3 additions & 0 deletions internal/model/netx.go
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,9 @@ type UnderlyingNetwork interface {
// GetaddrinfoResolverNetwork returns the resolver network.
GetaddrinfoResolverNetwork() string

// ListenTCP is equivalent to net.ListenTCP.
ListenTCP(network string, addr *net.TCPAddr) (net.Listener, error)

// ListenUDP is equivalent to net.ListenUDP.
ListenUDP(network string, addr *net.UDPAddr) (UDPLikeConn, error)
}
9 changes: 7 additions & 2 deletions internal/netxlite/dialer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"io"
"net"
"runtime"
"strings"
"sync"
"testing"
Expand Down Expand Up @@ -104,7 +105,8 @@ func TestDialerSystem(t *testing.T) {
defaultTp := &DefaultTProxy{}
tp := &mocks.UnderlyingNetwork{
MockDialTimeout: func() time.Duration {
// Note: this test is notoriously flaky on Windows
// Note: this test is notoriously flaky on Windows as documented by
// TODO(https://github.com/ooni/probe/issues/2537)
return time.Nanosecond
},
MockDialContext: defaultTp.DialContext,
Expand All @@ -115,7 +117,10 @@ func TestDialerSystem(t *testing.T) {
conn, err := d.DialContext(ctx, "tcp", "dns.google:443")
stop := time.Now()
if err == nil || !strings.HasSuffix(err.Error(), "i/o timeout") {
t.Fatal(err)
if runtime.GOOS == "windows" {
t.Skip("https://github.com/ooni/probe/issues/2537")
}
t.Fatal("unexpected error", err)
}
if conn != nil {
t.Fatal("unexpected conn")
Expand Down
8 changes: 8 additions & 0 deletions internal/netxlite/dnsovergetaddrinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ import (
"github.com/ooni/probe-cli/v3/internal/mocks"
)

func TestNewDNSOverGetaddrinfoTransport(t *testing.T) {
txp := NewDNSOverGetaddrinfoTransport()
underlying := txp.(*dnsOverGetaddrinfoTransport)
if underlying.provider.underlying != nil {
t.Fatal("expected to see a nil underlying network")
}
}

func TestDNSOverGetaddrinfo(t *testing.T) {
t.Run("RequiresPadding", func(t *testing.T) {
txp := &dnsOverGetaddrinfoTransport{}
Expand Down
5 changes: 5 additions & 0 deletions internal/netxlite/netem.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ func (a *NetemUnderlyingNetworkAdapter) GetaddrinfoResolverNetwork() string {
return a.UNet.GetaddrinfoResolverNetwork()
}

// ListenTCP implements model.UnderlyingNetwork
func (a *NetemUnderlyingNetworkAdapter) ListenTCP(network string, addr *net.TCPAddr) (net.Listener, error) {
return a.UNet.ListenTCP(network, addr)
}

// ListenUDP implements model.UnderlyingNetwork
func (a *NetemUnderlyingNetworkAdapter) ListenUDP(network string, addr *net.UDPAddr) (model.UDPLikeConn, error) {
return a.UNet.ListenUDP(network, addr)
Expand Down
63 changes: 63 additions & 0 deletions internal/netxlite/netem_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package netxlite

import (
"context"
"net"
"sync"
"testing"

"github.com/apex/log"
"github.com/ooni/netem"
"github.com/ooni/probe-cli/v3/internal/runtimex"
)

func TestNetemUnderlyingNetworkAdapter(t *testing.T) {

// This test case explicitly ensures we can use the adapter to listen for TCP
t.Run("ListenTCP", func(t *testing.T) {
// create a star network topology
topology := runtimex.Try1(netem.NewStarTopology(log.Log))
defer topology.Close()

// constants for the IP address we're using
const (
clientAddress = "130.192.91.211"
serverAddress = "93.184.216.34"
)

// create the stacks
serverStack := runtimex.Try1(topology.AddHost(serverAddress, "0.0.0.0", &netem.LinkConfig{}))
clientStack := runtimex.Try1(topology.AddHost(clientAddress, "0.0.0.0", &netem.LinkConfig{}))

// wrap the server stack and create listening socket
serverAdapter := &NetemUnderlyingNetworkAdapter{serverStack}
serverEndpoint := &net.TCPAddr{IP: net.ParseIP(serverAddress), Port: 54321}
listener := runtimex.Try1(serverAdapter.ListenTCP("tcp", serverEndpoint))
defer listener.Close()

// listen in a background goroutine
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
conn := runtimex.Try1(listener.Accept())
conn.Close()
wg.Done()
}()

// wrap the client stack
clientAdapter := &NetemUnderlyingNetworkAdapter{clientStack}

// connect in a background goroutine
wg.Add(1)
go func() {
ctx := context.Background()
conn := runtimex.Try1(clientAdapter.DialContext(ctx, "tcp", serverEndpoint.String()))
conn.Close()
wg.Done()
}()

// wait for all operations to complete
wg.Wait()
})

}
3 changes: 2 additions & 1 deletion internal/netxlite/netx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import (
"github.com/quic-go/quic-go/http3"
)

func TestNetx(t *testing.T) {
// This test ensures that a Netx wrapping a netem.UNet is WAI
func TestNetxWithNetem(t *testing.T) {
// create a star network topology
topology := runtimex.Try1(netem.NewStarTopology(log.Log))
defer topology.Close()
Expand Down
5 changes: 5 additions & 0 deletions internal/netxlite/tproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ func (tp *DefaultTProxy) DialContext(ctx context.Context, network, address strin
return d.DialContext(ctx, network, address)
}

// ListenTCP implements UnderlyingNetwork.
func (tp *DefaultTProxy) ListenTCP(network string, addr *net.TCPAddr) (net.Listener, error) {
return net.ListenTCP(network, addr)
}

// ListenUDP implements UnderlyingNetwork.
func (tp *DefaultTProxy) ListenUDP(network string, addr *net.UDPAddr) (model.UDPLikeConn, error) {
return net.ListenUDP(network, addr)
Expand Down
32 changes: 32 additions & 0 deletions internal/netxlite/tproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import (
"net"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"

"github.com/ooni/probe-cli/v3/internal/mocks"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/runtimex"
)

func TestTproxyNilSafeProvider(t *testing.T) {
Expand Down Expand Up @@ -99,3 +101,33 @@ func TestWithCustomTProxy(t *testing.T) {
})
})
}

// We generally do not listen here as part of other tests, since the listening
// functionality is mainly only use for testingx. So, here's a specific test for that.
func TestTproxyListenTCP(t *testing.T) {
tproxy := &DefaultTProxy{}

listener := runtimex.Try1(tproxy.ListenTCP("tcp", &net.TCPAddr{}))
serverEndpoint := listener.Addr().String()

// listen in a background goroutine
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
conn := runtimex.Try1(listener.Accept())
conn.Close()
wg.Done()
}()

// dial in a background goroutine
wg.Add(1)
go func() {
ctx := context.Background()
conn := runtimex.Try1(tproxy.DialContext(ctx, "tcp", serverEndpoint))
conn.Close()
wg.Done()
}()

// wait for the goroutines to finish
wg.Wait()
}