Skip to content

Commit

Permalink
feature(tlstool): very rough tool to experiment with SNI splitting
Browse files Browse the repository at this point in the history
First exploratory step towards #622.
  • Loading branch information
bassosimone committed Nov 11, 2020
1 parent ecbde00 commit 9e0d758
Show file tree
Hide file tree
Showing 6 changed files with 520 additions and 0 deletions.
13 changes: 13 additions & 0 deletions allexperiments.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/ooni/probe-engine/experiment/sniblocking"
"github.com/ooni/probe-engine/experiment/stunreachability"
"github.com/ooni/probe-engine/experiment/telegram"
"github.com/ooni/probe-engine/experiment/tlstool"
"github.com/ooni/probe-engine/experiment/tor"
"github.com/ooni/probe-engine/experiment/urlgetter"
"github.com/ooni/probe-engine/experiment/webconnectivity"
Expand Down Expand Up @@ -240,6 +241,18 @@ var experimentsByName = map[string]func(*Session) *ExperimentBuilder{
}
},

"tlstool": func(session *Session) *ExperimentBuilder {
return &ExperimentBuilder{
build: func(config interface{}) *Experiment {
return NewExperiment(session, tlstool.NewExperimentMeasurer(
*config.(*tlstool.Config),
))
},
config: &tlstool.Config{},
inputPolicy: InputRequired,
}
},

"tor": func(session *Session) *ExperimentBuilder {
return &ExperimentBuilder{
build: func(config interface{}) *Experiment {
Expand Down
73 changes: 73 additions & 0 deletions experiment/tlstool/internal/fake_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package internal

import (
"context"
"io"
"net"
"time"
)

type FakeDialer struct {
Conn net.Conn
Err error
}

func (d FakeDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
time.Sleep(10 * time.Microsecond)
return d.Conn, d.Err
}

type FakeConn struct {
ReadError error
ReadData []byte
SetDeadlineError error
SetReadDeadlineError error
SetWriteDeadlineError error
WriteData [][]byte
WriteError error
}

func (c *FakeConn) Read(b []byte) (int, error) {
if len(c.ReadData) > 0 {
n := copy(b, c.ReadData)
c.ReadData = c.ReadData[n:]
return n, nil
}
if c.ReadError != nil {
return 0, c.ReadError
}
return 0, io.EOF
}

func (c *FakeConn) Write(b []byte) (n int, err error) {
if c.WriteError != nil {
return 0, c.WriteError
}
c.WriteData = append(c.WriteData, b)
n = len(b)
return
}

func (*FakeConn) Close() (err error) {
return
}

func (*FakeConn) LocalAddr() net.Addr {
return &net.TCPAddr{}
}

func (*FakeConn) RemoteAddr() net.Addr {
return &net.TCPAddr{}
}

func (c *FakeConn) SetDeadline(t time.Time) (err error) {
return c.SetDeadlineError
}

func (c *FakeConn) SetReadDeadline(t time.Time) (err error) {
return c.SetReadDeadlineError
}

func (c *FakeConn) SetWriteDeadline(t time.Time) (err error) {
return c.SetWriteDeadlineError
}
55 changes: 55 additions & 0 deletions experiment/tlstool/internal/split.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package internal

import (
"bytes"
"context"
"net"
"time"

"github.com/ooni/probe-engine/netx"
)

// SplitDialer is a dialer that splits writes according to a
// pattern and may delay the second write of delay milliseconds.
type SplitDialer struct {
netx.Dialer
Delay int64
Pattern string
}

// DialContext implements netx.Dialer.DialContext.
func (d SplitDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
conn, err := d.Dialer.DialContext(ctx, network, address)
if err != nil {
return nil, err
}
return SplitConn{Conn: conn, Delay: d.Delay, Pattern: []byte(d.Pattern)}, nil
}

// SplitConn is a conn that splits writes.
type SplitConn struct {
net.Conn
BeforeSecondWrite func() // for testing
Delay int64
Pattern []byte
}

// Write implements net.Conn.Write.
func (c SplitConn) Write(b []byte) (int, error) {
idx := bytes.Index(b, c.Pattern)
if idx > -1 {
idx += len(c.Pattern) / 2
if _, err := c.Conn.Write(b[:idx]); err != nil {
return 0, err
}
<-time.After(time.Duration(c.Delay) * time.Millisecond)
if c.BeforeSecondWrite != nil {
c.BeforeSecondWrite()
}
if _, err := c.Conn.Write(b[idx:]); err != nil {
return 0, err
}
return len(b), nil
}
return c.Conn.Write(b)
}
177 changes: 177 additions & 0 deletions experiment/tlstool/internal/split_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package internal_test

import (
"context"
"errors"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/ooni/probe-engine/experiment/tlstool/internal"
)

func TestSplitDialerFailure(t *testing.T) {
expected := errors.New("mocked error")
d := internal.SplitDialer{Dialer: internal.FakeDialer{Err: expected}}
conn, err := d.DialContext(context.Background(), "tcp", "1.1.1.1:853")
if !errors.Is(err, expected) {
t.Fatalf("not the error we expected: %+v", err)
}
if conn != nil {
t.Fatal("expected nil conn here")
}
}

func TestSplitDialerSuccess(t *testing.T) {
innerconn := &internal.FakeConn{}
d := internal.SplitDialer{
Dialer: internal.FakeDialer{Conn: innerconn},
Delay: 1234,
Pattern: "abcdef",
}
conn, err := d.DialContext(context.Background(), "tcp", "1.1.1.1:853")
if err != nil {
t.Fatal(err)
}
realconn, ok := conn.(internal.SplitConn)
if !ok {
t.Fatal("cannot cast conn to internal.SplitConn")
}
if realconn.Delay != 1234 {
t.Fatal("invalid Delay value")
}
if diff := cmp.Diff(realconn.Pattern, []byte("abcdef")); diff != "" {
t.Fatal(diff)
}
if realconn.Conn != innerconn {
t.Fatal("invalid Conn value")
}
}

func TestWriteSuccessNoSplit(t *testing.T) {
const (
pattern = "abc.def"
data = "deadbeefdeafbeef"
)
innerconn := &internal.FakeConn{}
conn := internal.SplitConn{
Conn: innerconn,
Pattern: []byte(pattern),
}
count, err := conn.Write([]byte(data))
if err != nil {
t.Fatal(err)
}
if count != len(data) {
t.Fatal("invalid count")
}
if len(innerconn.WriteData) != 1 {
t.Fatal("invalid number of writes")
}
if string(innerconn.WriteData[0]) != data {
t.Fatal("written invalid data")
}
}

func TestWriteFailureNoSplit(t *testing.T) {
const (
pattern = "abc.def"
data = "deadbeefdeafbeef"
)
expected := errors.New("mocked error")
innerconn := &internal.FakeConn{
WriteError: expected,
}
conn := internal.SplitConn{
Conn: innerconn,
Pattern: []byte(pattern),
}
count, err := conn.Write([]byte(data))
if !errors.Is(err, expected) {
t.Fatalf("not the error we expected: %+v", err)
}
if count != 0 {
t.Fatal("invalid count")
}
}

func TestWriteSuccessSplit(t *testing.T) {
const (
pattern = "abc.def"
data = "deadbeefabc.defdeafbeef"
)
innerconn := &internal.FakeConn{}
conn := internal.SplitConn{
Conn: innerconn,
Pattern: []byte(pattern),
}
count, err := conn.Write([]byte(data))
if err != nil {
t.Fatal(err)
}
if count != len(data) {
t.Fatal("invalid count")
}
if len(innerconn.WriteData) != 2 {
t.Fatal("invalid number of writes")
}
if string(innerconn.WriteData[0]) != "deadbeefabc" {
t.Fatal("written invalid data")
}
if string(innerconn.WriteData[1]) != ".defdeafbeef" {
t.Fatal("written invalid data")
}
}

func TestWriteFailureSplitFirstWrite(t *testing.T) {
const (
pattern = "abc.def"
data = "deadbeefabc.defdeafbeef"
)
expected := errors.New("mocked error")
innerconn := &internal.FakeConn{
WriteError: expected,
}
conn := internal.SplitConn{
Conn: innerconn,
Pattern: []byte(pattern),
}
count, err := conn.Write([]byte(data))
if !errors.Is(err, expected) {
t.Fatalf("not the error we expected: %+v", err)
}
if count != 0 {
t.Fatal("invalid count")
}
if len(innerconn.WriteData) != 0 {
t.Fatal("some data has been written")
}
}

func TestWriteFailureSplitSecondWrite(t *testing.T) {
const (
pattern = "abc.def"
data = "deadbeefabc.defdeafbeef"
)
expected := errors.New("mocked error")
innerconn := &internal.FakeConn{}
conn := internal.SplitConn{
BeforeSecondWrite: func() {
innerconn.WriteError = expected // second write will then fail
},
Conn: innerconn,
Pattern: []byte(pattern),
}
count, err := conn.Write([]byte(data))
if !errors.Is(err, expected) {
t.Fatalf("not the error we expected: %+v", err)
}
if count != 0 {
t.Fatal("invalid count")
}
if len(innerconn.WriteData) != 1 {
t.Fatal("we expected to see just one write")
}
if string(innerconn.WriteData[0]) != "deadbeefabc" {
t.Fatal("written invalid data")
}
}
Loading

0 comments on commit 9e0d758

Please sign in to comment.