@@ -4,13 +4,21 @@ import (
44 "context"
55 "fmt"
66 "io"
7+ "net"
8+ "net/http"
9+ "sync"
710 "testing"
811
12+ "github.com/apex/log"
13+ "github.com/google/gopacket/layers"
14+ "github.com/ooni/netem"
915 "github.com/ooni/probe-cli/v3/internal/experiment/telegram"
1016 "github.com/ooni/probe-cli/v3/internal/experiment/urlgetter"
11- "github.com/ooni/probe-cli/v3/internal/legacy/mockable "
17+ "github.com/ooni/probe-cli/v3/internal/mocks "
1218 "github.com/ooni/probe-cli/v3/internal/model"
19+ "github.com/ooni/probe-cli/v3/internal/netemx"
1320 "github.com/ooni/probe-cli/v3/internal/netxlite"
21+ "github.com/ooni/probe-cli/v3/internal/runtimex"
1422)
1523
1624func TestNewExperimentMeasurer (t * testing.T ) {
@@ -23,66 +31,6 @@ func TestNewExperimentMeasurer(t *testing.T) {
2331 }
2432}
2533
26- func TestGood (t * testing.T ) {
27- measurer := telegram .NewExperimentMeasurer (telegram.Config {})
28- measurement := new (model.Measurement )
29- args := & model.ExperimentArgs {
30- Callbacks : model .NewPrinterCallbacks (model .DiscardLogger ),
31- Measurement : measurement ,
32- Session : & mockable.Session {
33- MockableLogger : model .DiscardLogger ,
34- },
35- }
36- err := measurer .Run (context .Background (), args )
37- if err != nil {
38- t .Fatal (err )
39- }
40- tk := measurement .TestKeys .(* telegram.TestKeys )
41- if tk .Agent != "redirect" {
42- t .Fatal ("unexpected Agent" )
43- }
44- if tk .FailedOperation != nil {
45- t .Fatal ("unexpected FailedOperation" )
46- }
47- if tk .Failure != nil {
48- t .Fatal ("unexpected Failure" )
49- }
50- if len (tk .NetworkEvents ) <= 0 {
51- t .Fatal ("no NetworkEvents?!" )
52- }
53- if len (tk .Queries ) <= 0 {
54- t .Fatal ("no Queries?!" )
55- }
56- if len (tk .Requests ) <= 0 {
57- t .Fatal ("no Requests?!" )
58- }
59- if len (tk .TCPConnect ) <= 0 {
60- t .Fatal ("no TCPConnect?!" )
61- }
62- if len (tk .TLSHandshakes ) <= 0 {
63- t .Fatal ("no TLSHandshakes?!" )
64- }
65- if tk .TelegramHTTPBlocking != false {
66- t .Fatal ("unexpected TelegramHTTPBlocking" )
67- }
68- if tk .TelegramTCPBlocking != false {
69- t .Fatal ("unexpected TelegramTCPBlocking" )
70- }
71- if tk .TelegramWebFailure != nil {
72- t .Fatal ("unexpected TelegramWebFailure" )
73- }
74- if tk .TelegramWebStatus != "ok" {
75- t .Fatal ("unexpected TelegramWebStatus" )
76- }
77- sk , err := measurer .GetSummaryKeys (measurement )
78- if err != nil {
79- t .Fatal (err )
80- }
81- if _ , ok := sk .(telegram.SummaryKeys ); ! ok {
82- t .Fatal ("invalid type for summary keys" )
83- }
84- }
85-
8634func TestUpdateWithNoAccessPointsBlocking (t * testing.T ) {
8735 tk := telegram .NewTestKeys ()
8836 tk .Update (urlgetter.MultiOutput {
@@ -317,3 +265,302 @@ func TestSummaryKeysWorksAsIntended(t *testing.T) {
317265 })
318266 }
319267}
268+
269+ // telegramWebAddr is the web.telegram.org IP address as of 2023-07-11
270+ const telegramWebAddr = "149.154.167.99"
271+
272+ // configureDNSWithAddr configures the given DNS config for web.telegram.org using the given addr.
273+ func configureDNSWithAddr (config * netem.DNSConfig , addr string ) {
274+ config .AddRecord ("web.telegram.org" , "web.telegram.org" , addr )
275+ }
276+
277+ // configureDNSWithDefaults configures the given DNS config for web.telegram.org using the default addr.
278+ func configureDNSWithDefaults (config * netem.DNSConfig ) {
279+ configureDNSWithAddr (config , telegramWebAddr )
280+ }
281+
282+ // telegramHTTPServerNetStackHandler is a [netemx.QAEnvNetStackHandler] that serves HTTP requests
283+ // on the given addr and ports 443 and 80 as required by the telegram nettest
284+ type telegramHTTPServerNetStackHandler struct {
285+ closers []io.Closer
286+ mu sync.Mutex
287+ }
288+
289+ var _ netemx.QAEnvNetStackHandler = & telegramHTTPServerNetStackHandler {}
290+
291+ // Close implements netemx.QAEnvNetStackHandler.
292+ func (nsh * telegramHTTPServerNetStackHandler ) Close () error {
293+ // make the method locked as requested by the documentation
294+ defer nsh .mu .Unlock ()
295+ nsh .mu .Lock ()
296+
297+ // close each of the closers
298+ for _ , closer := range nsh .closers {
299+ _ = closer .Close ()
300+ }
301+
302+ // be idempotent
303+ nsh .closers = []io.Closer {}
304+ return nil
305+ }
306+
307+ // Listen implements netemx.QAEnvNetStackHandler.
308+ func (nsh * telegramHTTPServerNetStackHandler ) Listen (stack * netem.UNetStack ) error {
309+ // make the method locked as requested by the documentation
310+ defer nsh .mu .Unlock ()
311+ nsh .mu .Lock ()
312+
313+ // we create an empty mux, which should cause a 404 for each webpage, which seems what
314+ // the servers used by telegram DC do as of 2023-07-11
315+ mux := http .NewServeMux ()
316+
317+ // listen on port 80
318+ nsh .listenPort (stack , mux , 80 )
319+
320+ // listen on port 443
321+ nsh .listenPort (stack , mux , 443 )
322+ return nil
323+ }
324+
325+ func (nsh * telegramHTTPServerNetStackHandler ) listenPort (stack * netem.UNetStack , mux * http.ServeMux , port uint16 ) {
326+ // create the listening address
327+ ipAddr := net .ParseIP (stack .IPAddress ())
328+ runtimex .Assert (ipAddr != nil , "expected valid IP address" )
329+ addr := & net.TCPAddr {IP : ipAddr , Port : int (port )}
330+ listener := runtimex .Try1 (stack .ListenTCP ("tcp" , addr ))
331+ srvr := & http.Server {Handler : mux }
332+
333+ // serve requests in a background goroutine
334+ go srvr .Serve (listener )
335+
336+ // make sure we track the server (the .Serve method will close the
337+ // listener once we close the server itself)
338+ nsh .closers = append (nsh .closers , srvr )
339+ }
340+
341+ // newQAEnvironment creates a QA environment for testing using the given addresses.
342+ func newQAEnvironment (ipaddrs ... string ) * netemx.QAEnv {
343+ // create a single handler for handling all the requests
344+ handler := & telegramHTTPServerNetStackHandler {
345+ closers : []io.Closer {},
346+ mu : sync.Mutex {},
347+ }
348+
349+ // create the options for constructing the env
350+ var options []netemx.QAEnvOption
351+ for _ , ipaddr := range ipaddrs {
352+ options = append (options , netemx .QAEnvOptionNetStack (ipaddr , handler ))
353+ }
354+
355+ // add explicit logging which helps to inspect the tests results
356+ options = append (options , netemx .QAEnvOptionLogger (log .Log ))
357+
358+ // add handler for telegram web (we're using a different-from-reality HTTP handler
359+ // but we're not testing for the returned webpage, so we should be fine)
360+ options = append (options , netemx .QAEnvOptionHTTPServer (telegramWebAddr , netemx .QAEnvDefaultHTTPHandler ()))
361+
362+ // create the environment proper with all the options
363+ env := netemx .NewQAEnv (options ... )
364+
365+ // register with all the possible resolvers the correct DNS records - registering again
366+ // inside individual tests will override the values we're setting here
367+ configureDNSWithDefaults (env .ISPResolverConfig ())
368+ configureDNSWithDefaults (env .OtherResolversConfig ())
369+ return env
370+ }
371+
372+ func TestMeasurerRun (t * testing.T ) {
373+ t .Run ("without DPI: expect success" , func (t * testing.T ) {
374+ // create a new test environment
375+ env := newQAEnvironment (telegram .DatacenterIPAddrs ... )
376+ defer env .Close ()
377+
378+ env .Do (func () {
379+ measurer := telegram .NewExperimentMeasurer (telegram.Config {})
380+ measurement := & model.Measurement {}
381+ args := & model.ExperimentArgs {
382+ Callbacks : model .NewPrinterCallbacks (log .Log ),
383+ Measurement : measurement ,
384+ Session : & mocks.Session {MockLogger : func () model.Logger { return log .Log }},
385+ }
386+ err := measurer .Run (context .Background (), args )
387+ if err != nil {
388+ t .Fatalf ("Unexpected error: %s" , err )
389+ }
390+ tk , _ := (measurement .TestKeys ).(* telegram.TestKeys )
391+ if tk .TelegramWebFailure != nil {
392+ t .Fatalf ("Unexpected Telegram Web failure %s" , * tk .TelegramWebFailure )
393+ }
394+ if tk .TelegramHTTPBlocking {
395+ t .Fatalf ("Unexpected HTTP blocking" )
396+ }
397+ if tk .TelegramTCPBlocking {
398+ t .Fatal ("Unexpected TCP blocking" )
399+ }
400+ if tk .Agent != "redirect" {
401+ t .Fatal ("unexpected Agent" )
402+ }
403+ if tk .FailedOperation != nil {
404+ t .Fatal ("unexpected FailedOperation" )
405+ }
406+ if tk .Failure != nil {
407+ t .Fatal ("unexpected Failure" )
408+ }
409+ if len (tk .NetworkEvents ) <= 0 {
410+ t .Fatal ("no NetworkEvents?!" )
411+ }
412+ if len (tk .Queries ) <= 0 {
413+ t .Fatal ("no Queries?!" )
414+ }
415+ if len (tk .Requests ) <= 0 {
416+ t .Fatal ("no Requests?!" )
417+ }
418+ if len (tk .TCPConnect ) <= 0 {
419+ t .Fatal ("no TCPConnect?!" )
420+ }
421+ if len (tk .TLSHandshakes ) <= 0 {
422+ t .Fatal ("no TLSHandshakes?!" )
423+ }
424+ if tk .TelegramWebStatus != "ok" {
425+ t .Fatal ("unexpected TelegramWebStatus" )
426+ }
427+ sk , err := measurer .GetSummaryKeys (measurement )
428+ if err != nil {
429+ t .Fatal (err )
430+ }
431+ if _ , ok := sk .(telegram.SummaryKeys ); ! ok {
432+ t .Fatal ("invalid type for summary keys" )
433+ }
434+ })
435+ })
436+
437+ t .Run ("with poisoned DNS: expect TelegramWebFailure" , func (t * testing.T ) {
438+ // create a new test environment
439+ env := newQAEnvironment (telegram .DatacenterIPAddrs ... )
440+ defer env .Close ()
441+
442+ // register bogon entries for web.telegram.org in the resolver's ISP
443+ env .ISPResolverConfig ().AddRecord ("web.telegram.org" , "web.telegram.org" , "10.10.34.35" )
444+
445+ env .Do (func () {
446+ measurer := telegram .NewExperimentMeasurer (telegram.Config {})
447+ measurement := & model.Measurement {}
448+ args := & model.ExperimentArgs {
449+ Callbacks : model .NewPrinterCallbacks (log .Log ),
450+ Measurement : measurement ,
451+ Session : & mocks.Session {MockLogger : func () model.Logger { return log .Log }},
452+ }
453+ err := measurer .Run (context .Background (), args )
454+ if err != nil {
455+ t .Fatalf ("Unexpected error: %s" , err )
456+ }
457+ tk , _ := (measurement .TestKeys ).(* telegram.TestKeys )
458+ if tk .TelegramWebFailure == nil {
459+ t .Fatalf ("Expected Web Failure but got none" )
460+ }
461+ if tk .TelegramHTTPBlocking {
462+ t .Fatal ("Unexpected HTTP blocking" )
463+ }
464+ if tk .TelegramTCPBlocking {
465+ t .Fatal ("Unexpected TCP blocking" )
466+ }
467+ })
468+ })
469+
470+ t .Run ("with DPI that drops TCP traffic towards telegram endpoint: expect Telegram(HTTP|TCP)Blocking" , func (t * testing.T ) {
471+ if testing .Short () {
472+ t .Skip ("skip test in short mode" )
473+ }
474+
475+ // overwrite global Datacenters, otherwise the test times out because there are too many endpoints
476+ orig := telegram .DatacenterIPAddrs
477+ telegram .DatacenterIPAddrs = []string {
478+ "149.154.175.50" ,
479+ }
480+ defer func () {
481+ telegram .DatacenterIPAddrs = orig
482+ }()
483+
484+ // create a new test environment
485+ env := newQAEnvironment (telegram .DatacenterIPAddrs ... )
486+ defer env .Close ()
487+
488+ // add DPI engine to emulate the censorship condition
489+ dpi := env .DPIEngine ()
490+ for _ , dc := range telegram .DatacenterIPAddrs {
491+ dpi .AddRule (& netem.DPIDropTrafficForServerEndpoint {
492+ Logger : log .Log ,
493+ ServerIPAddress : dc ,
494+ ServerPort : 80 ,
495+ ServerProtocol : layers .IPProtocolTCP ,
496+ })
497+ dpi .AddRule (& netem.DPIDropTrafficForServerEndpoint {
498+ Logger : log .Log ,
499+ ServerIPAddress : dc ,
500+ ServerPort : 443 ,
501+ ServerProtocol : layers .IPProtocolTCP ,
502+ })
503+ }
504+
505+ env .Do (func () {
506+ measurer := telegram .NewExperimentMeasurer (telegram.Config {})
507+ measurement := & model.Measurement {}
508+ args := & model.ExperimentArgs {
509+ Callbacks : model .NewPrinterCallbacks (log .Log ),
510+ Measurement : measurement ,
511+ Session : & mocks.Session {MockLogger : func () model.Logger { return log .Log }},
512+ }
513+ err := measurer .Run (context .Background (), args )
514+ if err != nil {
515+ t .Fatalf ("Unexpected error: %s" , err )
516+ }
517+ tk , _ := (measurement .TestKeys ).(* telegram.TestKeys )
518+ if tk .TelegramWebFailure != nil {
519+ t .Fatalf ("Unexpected Telegram Web failure %s" , * tk .TelegramWebFailure )
520+ }
521+ if ! tk .TelegramHTTPBlocking {
522+ t .Fatal ("Expected HTTP blocking but got none" )
523+ }
524+ if ! tk .TelegramTCPBlocking {
525+ t .Fatal ("Expected TCP blocking but got none" )
526+ }
527+ })
528+ })
529+
530+ t .Run ("with DPI that drops TLS traffic with SNI = web.telegram.org: expect TelegramWebFailure" , func (t * testing.T ) {
531+ // create a new test environment
532+ env := newQAEnvironment (telegram .DatacenterIPAddrs ... )
533+ defer env .Close ()
534+
535+ // add DPI engine to emulate the censorship condition
536+ dpi := env .DPIEngine ()
537+ dpi .AddRule (& netem.DPIResetTrafficForTLSSNI {
538+ Logger : log .Log ,
539+ SNI : "web.telegram.org" ,
540+ })
541+
542+ env .Do (func () {
543+ measurer := telegram .NewExperimentMeasurer (telegram.Config {})
544+ measurement := & model.Measurement {}
545+ args := & model.ExperimentArgs {
546+ Callbacks : model .NewPrinterCallbacks (log .Log ),
547+ Measurement : measurement ,
548+ Session : & mocks.Session {MockLogger : func () model.Logger { return log .Log }},
549+ }
550+ err := measurer .Run (context .Background (), args )
551+ if err != nil {
552+ t .Fatalf ("Unexpected error: %s" , err )
553+ }
554+ tk , _ := (measurement .TestKeys ).(* telegram.TestKeys )
555+ if tk .TelegramWebFailure == nil {
556+ t .Fatalf ("Expected Web Failure but got none" )
557+ }
558+ if tk .TelegramHTTPBlocking {
559+ t .Fatal ("Unexpected HTTP blocking" )
560+ }
561+ if tk .TelegramTCPBlocking {
562+ t .Fatal ("Unexpected TCP blocking" )
563+ }
564+ })
565+ })
566+ }
0 commit comments