Skip to content

Commit

Permalink
refactor(dslx): start making functions stateless (#1379)
Browse files Browse the repository at this point in the history
This diff is pretty messy unless you use `git diff -w`, in which case
you see changes are actually minimal.

With this diff, we implement roughly 50% of
ooni/probe#2612. I have another couple of
diffs ready to finish off this issue's work, but committing all of them
together would create too much noise.
  • Loading branch information
bassosimone authored Oct 25, 2023
1 parent b528666 commit 600c7f6
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 366 deletions.
193 changes: 84 additions & 109 deletions internal/dslx/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,117 +72,92 @@ type ResolvedAddresses struct {
// DNSLookupGetaddrinfo returns a function that resolves a domain name to
// IP addresses using libc's getaddrinfo function.
func DNSLookupGetaddrinfo(rt Runtime) Func[*DomainToResolve, *Maybe[*ResolvedAddresses]] {
return &dnsLookupGetaddrinfoFunc{rt}
}

// dnsLookupGetaddrinfoFunc is the function returned by DNSLookupGetaddrinfo.
type dnsLookupGetaddrinfoFunc struct {
rt Runtime
}

// Apply implements Func.
func (f *dnsLookupGetaddrinfoFunc) Apply(
ctx context.Context, input *DomainToResolve) *Maybe[*ResolvedAddresses] {

// create trace
trace := f.rt.NewTrace(f.rt.IDGenerator().Add(1), f.rt.ZeroTime(), input.Tags...)

// start the operation logger
ol := logx.NewOperationLogger(
f.rt.Logger(),
"[#%d] DNSLookup[getaddrinfo] %s",
trace.Index(),
input.Domain,
)

// setup
const timeout = 4 * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

// create the resolver
resolver := trace.NewStdlibResolver(f.rt.Logger())

// lookup
addrs, err := resolver.LookupHost(ctx, input.Domain)

// stop the operation logger
ol.Stop(err)

state := &ResolvedAddresses{
Addresses: addrs, // maybe empty
Domain: input.Domain,
Trace: trace,
}

return &Maybe[*ResolvedAddresses]{
Error: err,
Observations: maybeTraceToObservations(trace),
Operation: netxlite.ResolveOperation,
State: state,
}
return FuncAdapter[*DomainToResolve, *Maybe[*ResolvedAddresses]](func(ctx context.Context, input *DomainToResolve) *Maybe[*ResolvedAddresses] {
// create trace
trace := rt.NewTrace(rt.IDGenerator().Add(1), rt.ZeroTime(), input.Tags...)

// start the operation logger
ol := logx.NewOperationLogger(
rt.Logger(),
"[#%d] DNSLookup[getaddrinfo] %s",
trace.Index(),
input.Domain,
)

// setup
const timeout = 4 * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

// create the resolver
resolver := trace.NewStdlibResolver(rt.Logger())

// lookup
addrs, err := resolver.LookupHost(ctx, input.Domain)

// stop the operation logger
ol.Stop(err)

state := &ResolvedAddresses{
Addresses: addrs, // maybe empty
Domain: input.Domain,
Trace: trace,
}

return &Maybe[*ResolvedAddresses]{
Error: err,
Observations: maybeTraceToObservations(trace),
Operation: netxlite.ResolveOperation,
State: state,
}
})
}

// DNSLookupUDP returns a function that resolves a domain name to
// IP addresses using the given DNS-over-UDP resolver.
func DNSLookupUDP(rt Runtime, resolver string) Func[*DomainToResolve, *Maybe[*ResolvedAddresses]] {
return &dnsLookupUDPFunc{
Resolver: resolver,
rt: rt,
}
}

// dnsLookupUDPFunc is the function returned by DNSLookupUDP.
type dnsLookupUDPFunc struct {
// Resolver is the MANDATORY endpointed of the resolver to use.
Resolver string
rt Runtime
}

// Apply implements Func.
func (f *dnsLookupUDPFunc) Apply(
ctx context.Context, input *DomainToResolve) *Maybe[*ResolvedAddresses] {

// create trace
trace := f.rt.NewTrace(f.rt.IDGenerator().Add(1), f.rt.ZeroTime(), input.Tags...)

// start the operation logger
ol := logx.NewOperationLogger(
f.rt.Logger(),
"[#%d] DNSLookup[%s/udp] %s",
trace.Index(),
f.Resolver,
input.Domain,
)

// setup
const timeout = 4 * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

// create the resolver
resolver := trace.NewParallelUDPResolver(
f.rt.Logger(),
trace.NewDialerWithoutResolver(f.rt.Logger()),
f.Resolver,
)

// lookup
addrs, err := resolver.LookupHost(ctx, input.Domain)

// stop the operation logger
ol.Stop(err)

state := &ResolvedAddresses{
Addresses: addrs, // maybe empty
Domain: input.Domain,
Trace: trace,
}

return &Maybe[*ResolvedAddresses]{
Error: err,
Observations: maybeTraceToObservations(trace),
Operation: netxlite.ResolveOperation,
State: state,
}
func DNSLookupUDP(rt Runtime, endpoint string) Func[*DomainToResolve, *Maybe[*ResolvedAddresses]] {
return FuncAdapter[*DomainToResolve, *Maybe[*ResolvedAddresses]](func(ctx context.Context, input *DomainToResolve) *Maybe[*ResolvedAddresses] {
// create trace
trace := rt.NewTrace(rt.IDGenerator().Add(1), rt.ZeroTime(), input.Tags...)

// start the operation logger
ol := logx.NewOperationLogger(
rt.Logger(),
"[#%d] DNSLookup[%s/udp] %s",
trace.Index(),
endpoint,
input.Domain,
)

// setup
const timeout = 4 * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

// create the resolver
resolver := trace.NewParallelUDPResolver(
rt.Logger(),
trace.NewDialerWithoutResolver(rt.Logger()),
endpoint,
)

// lookup
addrs, err := resolver.LookupHost(ctx, input.Domain)

// stop the operation logger
ol.Stop(err)

state := &ResolvedAddresses{
Addresses: addrs, // maybe empty
Domain: input.Domain,
Trace: trace,
}

return &Maybe[*ResolvedAddresses]{
Error: err,
Observations: maybeTraceToObservations(trace),
Operation: netxlite.ResolveOperation,
State: state,
}
})
}
51 changes: 18 additions & 33 deletions internal/dslx/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,16 @@ Test cases:
- with success
*/
func TestGetaddrinfo(t *testing.T) {
t.Run("Get dnsLookupGetaddrinfoFunc", func(t *testing.T) {
f := DNSLookupGetaddrinfo(NewMinimalRuntime(model.DiscardLogger, time.Now()))
if _, ok := f.(*dnsLookupGetaddrinfoFunc); !ok {
t.Fatal("unexpected type, want dnsLookupGetaddrinfoFunc")
}
})

t.Run("Apply dnsLookupGetaddrinfoFunc", func(t *testing.T) {
domain := &DomainToResolve{
Domain: "example.com",
Tags: []string{"antani"},
}

t.Run("with nil resolver", func(t *testing.T) {
f := dnsLookupGetaddrinfoFunc{
rt: NewMinimalRuntime(model.DiscardLogger, time.Now()),
}
f := DNSLookupGetaddrinfo(
NewMinimalRuntime(model.DiscardLogger, time.Now()),
)
ctx, cancel := context.WithCancel(context.Background())
cancel() // immediately cancel the lookup
res := f.Apply(ctx, domain)
Expand All @@ -84,8 +77,8 @@ func TestGetaddrinfo(t *testing.T) {

t.Run("with lookup error", func(t *testing.T) {
mockedErr := errors.New("mocked")
f := dnsLookupGetaddrinfoFunc{
rt: NewMinimalRuntime(model.DiscardLogger, time.Now(), MinimalRuntimeOptionMeasuringNetwork(&mocks.MeasuringNetwork{
f := DNSLookupGetaddrinfo(
NewMinimalRuntime(model.DiscardLogger, time.Now(), MinimalRuntimeOptionMeasuringNetwork(&mocks.MeasuringNetwork{
MockNewStdlibResolver: func(logger model.DebugLogger) model.Resolver {
return &mocks.Resolver{
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
Expand All @@ -94,7 +87,7 @@ func TestGetaddrinfo(t *testing.T) {
}
},
})),
}
)
res := f.Apply(context.Background(), domain)
if res.Observations == nil || len(res.Observations) <= 0 {
t.Fatal("unexpected empty observations")
Expand All @@ -111,8 +104,8 @@ func TestGetaddrinfo(t *testing.T) {
})

t.Run("with success", func(t *testing.T) {
f := dnsLookupGetaddrinfoFunc{
rt: NewRuntimeMeasurexLite(model.DiscardLogger, time.Now(), RuntimeMeasurexLiteOptionMeasuringNetwork(&mocks.MeasuringNetwork{
f := DNSLookupGetaddrinfo(
NewRuntimeMeasurexLite(model.DiscardLogger, time.Now(), RuntimeMeasurexLiteOptionMeasuringNetwork(&mocks.MeasuringNetwork{
MockNewStdlibResolver: func(logger model.DebugLogger) model.Resolver {
return &mocks.Resolver{
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
Expand All @@ -121,7 +114,7 @@ func TestGetaddrinfo(t *testing.T) {
}
},
})),
}
)
res := f.Apply(context.Background(), domain)
if res.Observations == nil || len(res.Observations) <= 0 {
t.Fatal("unexpected empty observations")
Expand Down Expand Up @@ -151,22 +144,14 @@ Test cases:
- with success
*/
func TestLookupUDP(t *testing.T) {
t.Run("Get dnsLookupUDPFunc", func(t *testing.T) {
rt := NewMinimalRuntime(model.DiscardLogger, time.Now())
f := DNSLookupUDP(rt, "1.1.1.1:53")
if _, ok := f.(*dnsLookupUDPFunc); !ok {
t.Fatal("unexpected type, want dnsLookupUDPFunc")
}
})

t.Run("Apply dnsLookupGetaddrinfoFunc", func(t *testing.T) {
domain := &DomainToResolve{
Domain: "example.com",
Tags: []string{"antani"},
}

t.Run("with nil resolver", func(t *testing.T) {
f := dnsLookupUDPFunc{Resolver: "1.1.1.1:53", rt: NewMinimalRuntime(model.DiscardLogger, time.Now())}
f := DNSLookupUDP(NewMinimalRuntime(model.DiscardLogger, time.Now()), "1.1.1.1:53")
ctx, cancel := context.WithCancel(context.Background())
cancel()
res := f.Apply(ctx, domain)
Expand All @@ -180,9 +165,8 @@ func TestLookupUDP(t *testing.T) {

t.Run("with lookup error", func(t *testing.T) {
mockedErr := errors.New("mocked")
f := dnsLookupUDPFunc{
Resolver: "1.1.1.1:53",
rt: NewMinimalRuntime(model.DiscardLogger, time.Now(), MinimalRuntimeOptionMeasuringNetwork(&mocks.MeasuringNetwork{
f := DNSLookupUDP(
NewMinimalRuntime(model.DiscardLogger, time.Now(), MinimalRuntimeOptionMeasuringNetwork(&mocks.MeasuringNetwork{
MockNewParallelUDPResolver: func(logger model.DebugLogger, dialer model.Dialer, endpoint string) model.Resolver {
return &mocks.Resolver{
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
Expand All @@ -198,7 +182,8 @@ func TestLookupUDP(t *testing.T) {
}
},
})),
}
"1.1.1.1:53",
)
res := f.Apply(context.Background(), domain)
if res.Observations == nil || len(res.Observations) <= 0 {
t.Fatal("unexpected empty observations")
Expand All @@ -215,9 +200,8 @@ func TestLookupUDP(t *testing.T) {
})

t.Run("with success", func(t *testing.T) {
f := dnsLookupUDPFunc{
Resolver: "1.1.1.1:53",
rt: NewRuntimeMeasurexLite(model.DiscardLogger, time.Now(), RuntimeMeasurexLiteOptionMeasuringNetwork(&mocks.MeasuringNetwork{
f := DNSLookupUDP(
NewRuntimeMeasurexLite(model.DiscardLogger, time.Now(), RuntimeMeasurexLiteOptionMeasuringNetwork(&mocks.MeasuringNetwork{
MockNewParallelUDPResolver: func(logger model.DebugLogger, dialer model.Dialer, address string) model.Resolver {
return &mocks.Resolver{
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
Expand All @@ -233,7 +217,8 @@ func TestLookupUDP(t *testing.T) {
}
},
})),
}
"1.1.1.1:53",
)
res := f.Apply(context.Background(), domain)
if res.Observations == nil || len(res.Observations) <= 0 {
t.Fatal("unexpected empty observations")
Expand Down
8 changes: 8 additions & 0 deletions internal/dslx/fxcore.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ type Func[A, B any] interface {
Apply(ctx context.Context, a A) B
}

// FuncAdapter adapts a func to be a Func.
type FuncAdapter[A, B any] func(ctx context.Context, a A) B

// Apply implements Func.
func (fa FuncAdapter[A, B]) Apply(ctx context.Context, a A) B {
return fa(ctx, a)
}

// Maybe is the result of an operation implemented by this package
// that may fail such as [TCPConnect] or [TLSHandshake].
type Maybe[State any] struct {
Expand Down
Loading

0 comments on commit 600c7f6

Please sign in to comment.