-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature(tlstool): very rough tool to experiment with SNI splitting
First exploratory step towards #622.
- Loading branch information
1 parent
ecbde00
commit 9e0d758
Showing
6 changed files
with
520 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
Oops, something went wrong.