diff --git a/protocol/shadowsocks_stream/ssr_cipher_test.go b/protocol/shadowsocks_stream/ssr_cipher_test.go new file mode 100644 index 0000000..a59e0d3 --- /dev/null +++ b/protocol/shadowsocks_stream/ssr_cipher_test.go @@ -0,0 +1,73 @@ +package shadowsocks_stream + +import ( + "io" + "net" + "testing" + "time" + + "github.com/daeuniverse/outbound/ciphers" + "github.com/daeuniverse/outbound/netproxy" +) + +// obfsStub stands in for transport/shadowsocksr/obfs.Conn: it records the +// cipher and address length handed down by the shadowsocks stream layer. +type obfsStub struct { + netproxy.Conn + cipher *ciphers.StreamCipher + addrLen int +} + +func (o *obfsStub) SetCipher(cipher *ciphers.StreamCipher) { o.cipher = cipher } +func (o *obfsStub) SetAddrLen(addrLen int) { o.addrLen = addrLen } + +type discardConn struct{ net.Conn } + +func (discardConn) Read(p []byte) (int, error) { return 0, io.EOF } +func (discardConn) Write(p []byte) (int, error) { return len(p), nil } +func (discardConn) Close() error { return nil } +func (discardConn) SetDeadline(time.Time) error { return nil } +func (discardConn) SetReadDeadline(time.Time) error { return nil } +func (discardConn) SetWriteDeadline(t time.Time) error { + return nil +} + +func newStubChain(t *testing.T, wrap bool) (*TcpConn, *obfsStub) { + t.Helper() + cipher, err := ciphers.NewStreamCipher("aes-256-cfb", "p@ssw0rd") + if err != nil { + t.Fatal(err) + } + stub := &obfsStub{Conn: discardConn{}} + var under netproxy.Conn = stub + if wrap { + under = netproxy.NewBufferedReaderConn(stub, 0) + } + return NewTcpConn(under, cipher), stub +} + +func TestSSRObfsReceivesCipherWithoutWrapper(t *testing.T) { + conn, stub := newStubChain(t, false) + if _, err := conn.Write([]byte("hello")); err != nil { + t.Fatal(err) + } + if stub.cipher == nil { + t.Fatal("obfs conn did not receive the cipher") + } + if stub.addrLen != len("hello") { + t.Fatalf("addrLen = %d, want %d", stub.addrLen, len("hello")) + } +} + +func TestSSRObfsReceivesCipherThroughBufferedReaderConn(t *testing.T) { + conn, stub := newStubChain(t, true) + if _, err := conn.Write([]byte("hello")); err != nil { + t.Fatal(err) + } + if stub.cipher == nil { + t.Fatal("obfs conn behind BufferedReaderConn did not receive the cipher") + } + if stub.addrLen != len("hello") { + t.Fatalf("addrLen = %d, want %d", stub.addrLen, len("hello")) + } +} diff --git a/protocol/shadowsocks_stream/tcp_conn.go b/protocol/shadowsocks_stream/tcp_conn.go index 3afb724..e4e2f1d 100644 --- a/protocol/shadowsocks_stream/tcp_conn.go +++ b/protocol/shadowsocks_stream/tcp_conn.go @@ -24,6 +24,17 @@ func NewTcpConn(c netproxy.Conn, cipher *ciphers.StreamCipher) *TcpConn { } } +// unwrapConn peels transparent wrappers such as netproxy.BufferedReaderConn so +// that SSR obfs conns stay reachable by the type assertions below. +func unwrapConn(c netproxy.Conn) netproxy.Conn { + if ic, ok := c.(interface{ IntrinsicConn() netproxy.Conn }); ok { + if inner := ic.IntrinsicConn(); inner != nil { + return inner + } + } + return c +} + func (c *TcpConn) Read(b []byte) (n int, err error) { if !c.cipher.DecryptInited() { buf := b @@ -82,12 +93,13 @@ func (c *TcpConn) Write(b []byte) (n int, err error) { b = buf // For SSR obfs. - if innerConn, ok := c.Conn.(interface { + obfsConn := unwrapConn(c.Conn) + if innerConn, ok := obfsConn.(interface { SetCipher(cipher *ciphers.StreamCipher) }); ok { innerConn.SetCipher(c.cipher) } - if innerConn, ok := c.Conn.(interface { + if innerConn, ok := obfsConn.(interface { SetAddrLen(addrLen int) }); ok { innerConn.SetAddrLen(lenToWrite)