Skip to content

Commit

Permalink
feature(tlstool): add 8:4:rest segmenter
Browse files Browse the repository at this point in the history
See #622
  • Loading branch information
bassosimone committed Nov 11, 2020
1 parent 24aa4b0 commit 6865ead
Show file tree
Hide file tree
Showing 7 changed files with 433 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,40 @@ import (
"github.com/ooni/probe-engine/netx"
)

// SplitDialer is a dialer that splits TCP segments in the middle of
// Dialer is a dialer that splits TCP segments in the middle of
// a specific byte pattern and may optionally delay writing the second
// half of the segment by a specific number of milliseconds.
type SplitDialer struct {
type Dialer 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) {
func (d Dialer) 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
return Conn{Conn: conn, Delay: d.Delay, Pattern: []byte(d.Pattern)}, nil
}

// SplitConn is the net.Conn generated by SplitDialer.
// Conn is the net.Conn generated by patternsplitter.Dialer.
//
// Caveat
//
// The connection will keep splitting segments when it sees the
// specified byte pattern. This behaviour is fine as long as we're
// just checking whether the TLS handshake works.
type SplitConn struct {
type Conn struct {
net.Conn
BeforeSecondWrite func() // for testing
Delay int64
Pattern []byte
}

// Write implements net.Conn.Write.
func (c SplitConn) Write(b []byte) (int, error) {
func (c Conn) Write(b []byte) (int, error) {
if idx := bytes.Index(b, c.Pattern); idx > -1 {
idx += len(c.Pattern) / 2
if _, err := c.Conn.Write(b[:idx]); err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (
"github.com/ooni/probe-engine/experiment/tlstool/internal/patternsplitter"
)

func TestSplitDialerFailure(t *testing.T) {
func TestDialerFailure(t *testing.T) {
expected := errors.New("mocked error")
d := patternsplitter.SplitDialer{Dialer: patternsplitter.FakeDialer{Err: expected}}
d := patternsplitter.Dialer{Dialer: patternsplitter.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)
Expand All @@ -21,9 +21,9 @@ func TestSplitDialerFailure(t *testing.T) {
}
}

func TestSplitDialerSuccess(t *testing.T) {
func TestDialerSuccess(t *testing.T) {
innerconn := &patternsplitter.FakeConn{}
d := patternsplitter.SplitDialer{
d := patternsplitter.Dialer{
Dialer: patternsplitter.FakeDialer{Conn: innerconn},
Delay: 1234,
Pattern: "abcdef",
Expand All @@ -32,7 +32,7 @@ func TestSplitDialerSuccess(t *testing.T) {
if err != nil {
t.Fatal(err)
}
realconn, ok := conn.(patternsplitter.SplitConn)
realconn, ok := conn.(patternsplitter.Conn)
if !ok {
t.Fatal("cannot cast conn to patternsplitter.SplitConn")
}
Expand All @@ -53,7 +53,7 @@ func TestWriteSuccessNoSplit(t *testing.T) {
data = "deadbeefdeafbeef"
)
innerconn := &patternsplitter.FakeConn{}
conn := patternsplitter.SplitConn{
conn := patternsplitter.Conn{
Conn: innerconn,
Pattern: []byte(pattern),
}
Expand Down Expand Up @@ -81,7 +81,7 @@ func TestWriteFailureNoSplit(t *testing.T) {
innerconn := &patternsplitter.FakeConn{
WriteError: expected,
}
conn := patternsplitter.SplitConn{
conn := patternsplitter.Conn{
Conn: innerconn,
Pattern: []byte(pattern),
}
Expand All @@ -100,7 +100,7 @@ func TestWriteSuccessSplit(t *testing.T) {
data = "deadbeefabc.defdeafbeef"
)
innerconn := &patternsplitter.FakeConn{}
conn := patternsplitter.SplitConn{
conn := patternsplitter.Conn{
Conn: innerconn,
Pattern: []byte(pattern),
}
Expand Down Expand Up @@ -131,7 +131,7 @@ func TestWriteFailureSplitFirstWrite(t *testing.T) {
innerconn := &patternsplitter.FakeConn{
WriteError: expected,
}
conn := patternsplitter.SplitConn{
conn := patternsplitter.Conn{
Conn: innerconn,
Pattern: []byte(pattern),
}
Expand All @@ -154,7 +154,7 @@ func TestWriteFailureSplitSecondWrite(t *testing.T) {
)
expected := errors.New("mocked error")
innerconn := &patternsplitter.FakeConn{}
conn := patternsplitter.SplitConn{
conn := patternsplitter.Conn{
BeforeSecondWrite: func() {
innerconn.WriteError = expected // second write will then fail
},
Expand Down
73 changes: 73 additions & 0 deletions experiment/tlstool/internal/segmenter/fake_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package segmenter

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
}
70 changes: 70 additions & 0 deletions experiment/tlstool/internal/segmenter/segmenter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Package segmenter contains code that splits TCP data in three
// segments as described by Kevin Boch in https://youtu.be/ksojSRFLbBM?t=1140.
package segmenter

import (
"context"
"net"
"time"

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

// Dialer creates connections where we split TCP data in three
// segments as mentioned above. We optionally also delay writing
// each segment by a specific number of milliseconds.
type Dialer struct {
netx.Dialer
Delay int64
}

// DialContext implements netx.Dialer.DialContext.
func (d Dialer) 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 Conn{Conn: conn, Delay: d.Delay}, nil
}

// Conn is the net.Conn generated by patternsplitter.Dialer.
//
// Caveat
//
// The connection will keep splitting segments forever. This behaviour
// is fine as long as we're just checking whether the TLS handshake works.
type Conn struct {
net.Conn
BeforeSecondWrite func() // for testing
BeforeThirdWrite func() // for testing
Delay int64
}

// Write implements net.Conn.Write.
func (c Conn) Write(b []byte) (int, error) {
const (
first = 8
second = 12
)
if len(b) > second {
if _, err := c.Conn.Write(b[:first]); 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[first:second]); err != nil {
return 0, err
}
<-time.After(time.Duration(c.Delay) * time.Millisecond)
if c.BeforeThirdWrite != nil {
c.BeforeThirdWrite()
}
if _, err := c.Conn.Write(b[second:]); err != nil {
return 0, err
}
return len(b), nil
}
return c.Conn.Write(b)
}

0 comments on commit 6865ead

Please sign in to comment.