diff options
author | Yawning Angel <yawning@torproject.org> | 2015-04-12 19:00:46 +0000 |
---|---|---|
committer | Yawning Angel <yawning@torproject.org> | 2015-04-15 20:50:01 +0000 |
commit | a8d7134f1097bd50803da0e2a86c07524e433b51 (patch) | |
tree | 346403d6ecfdf2a76d8e736862c1b42d165cc919 /common/socks5/socks_test.go | |
parent | 8996cb2646f2721b2d86f5f6b54b5c21d2acc71d (diff) |
Use a built in SOCKS 5 server instead of goptlibs.
Differences from my goptlib branch:
* Instead of exposing a net.Listener, just expose a Handshake() routine
that takes an existing net.Conn. (#14135 is irrelevant to this socks
server.
* There's an extra routine for sending back sensible errors on Dial
failure instead of "General failure".
* The code is slightly cleaner (IMO).
Gotchas:
* If the goptlib pt.Args datatype or external interface changes,
args.go will need to be updated.
Tested with obfs3 and obfs4, including IPv6.
Diffstat (limited to 'common/socks5/socks_test.go')
-rw-r--r-- | common/socks5/socks_test.go | 412 |
1 files changed, 412 insertions, 0 deletions
diff --git a/common/socks5/socks_test.go b/common/socks5/socks_test.go new file mode 100644 index 0000000..720476f --- /dev/null +++ b/common/socks5/socks_test.go @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2015, Yawning Angel <yawning at torproject dot org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package socks5 + +import ( + "bufio" + "bytes" + "encoding/hex" + "io" + "net" + "testing" +) + +func tcpAddrsEqual(a, b *net.TCPAddr) bool { + return a.IP.Equal(b.IP) && a.Port == b.Port +} + +// testReadWriter is a bytes.Buffer backed io.ReadWriter used for testing. The +// Read and Write routines are to be used by the component being tested. Data +// can be written to and read back via the writeHex and readHex routines. +type testReadWriter struct { + readBuf bytes.Buffer + writeBuf bytes.Buffer +} + +func (c *testReadWriter) Read(buf []byte) (n int, err error) { + return c.readBuf.Read(buf) +} + +func (c *testReadWriter) Write(buf []byte) (n int, err error) { + return c.writeBuf.Write(buf) +} + +func (c *testReadWriter) writeHex(str string) (n int, err error) { + var buf []byte + if buf, err = hex.DecodeString(str); err != nil { + return + } + return c.readBuf.Write(buf) +} + +func (c *testReadWriter) readHex() string { + return hex.EncodeToString(c.writeBuf.Bytes()) +} + +func (c *testReadWriter) toBufio() *bufio.ReadWriter { + return bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c)) +} + +func (c *testReadWriter) toRequest() *Request { + req := new(Request) + req.rw = c.toBufio() + return req +} + +func (c *testReadWriter) reset(req *Request) { + c.readBuf.Reset() + c.writeBuf.Reset() + req.rw = c.toBufio() +} + +// TestAuthInvalidVersion tests auth negotiation with an invalid version. +func TestAuthInvalidVersion(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + + // VER = 03, NMETHODS = 01, METHODS = [00] + c.writeHex("030100") + if _, err := req.negotiateAuth(); err == nil { + t.Error("negotiateAuth(InvalidVersion) succeded") + } +} + +// TestAuthInvalidNMethods tests auth negotiaton with no methods. +func TestAuthInvalidNMethods(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + var err error + var method byte + + // VER = 05, NMETHODS = 00 + c.writeHex("0500") + if method, err = req.negotiateAuth(); err != nil { + t.Error("negotiateAuth(No Methods) failed:", err) + } + if method != authNoAcceptableMethods { + t.Error("negotiateAuth(No Methods) picked unexpected method:", method) + } + if msg := c.readHex(); msg != "05ff" { + t.Error("negotiateAuth(No Methods) invalid response:", msg) + } +} + +// TestAuthNoneRequired tests auth negotiaton with NO AUTHENTICATION REQUIRED. +func TestAuthNoneRequired(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + var err error + var method byte + + // VER = 05, NMETHODS = 01, METHODS = [00] + c.writeHex("050100") + if method, err = req.negotiateAuth(); err != nil { + t.Error("negotiateAuth(None) failed:", err) + } + if method != authNoneRequired { + t.Error("negotiateAuth(None) unexpected method:", method) + } + if msg := c.readHex(); msg != "0500" { + t.Error("negotiateAuth(None) invalid response:", msg) + } +} + +// TestAuthUsernamePassword tests auth negotiation with USERNAME/PASSWORD. +func TestAuthUsernamePassword(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + var err error + var method byte + + // VER = 05, NMETHODS = 01, METHODS = [02] + c.writeHex("050102") + if method, err = req.negotiateAuth(); err != nil { + t.Error("negotiateAuth(UsernamePassword) failed:", err) + } + if method != authUsernamePassword { + t.Error("negotiateAuth(UsernamePassword) unexpected method:", method) + } + if msg := c.readHex(); msg != "0502" { + t.Error("negotiateAuth(UsernamePassword) invalid response:", msg) + } +} + +// TestAuthBoth tests auth negotiation containing both NO AUTHENTICATION +// REQUIRED and USERNAME/PASSWORD. +func TestAuthBoth(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + var err error + var method byte + + // VER = 05, NMETHODS = 02, METHODS = [00, 02] + c.writeHex("05020002") + if method, err = req.negotiateAuth(); err != nil { + t.Error("negotiateAuth(Both) failed:", err) + } + if method != authUsernamePassword { + t.Error("negotiateAuth(Both) unexpected method:", method) + } + if msg := c.readHex(); msg != "0502" { + t.Error("negotiateAuth(Both) invalid response:", msg) + } +} + +// TestAuthUnsupported tests auth negotiation with a unsupported method. +func TestAuthUnsupported(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + var err error + var method byte + + // VER = 05, NMETHODS = 01, METHODS = [01] (GSSAPI) + c.writeHex("050101") + if method, err = req.negotiateAuth(); err != nil { + t.Error("negotiateAuth(Unknown) failed:", err) + } + if method != authNoAcceptableMethods { + t.Error("negotiateAuth(Unknown) picked unexpected method:", method) + } + if msg := c.readHex(); msg != "05ff" { + t.Error("negotiateAuth(Unknown) invalid response:", msg) + } +} + +// TestAuthUnsupported2 tests auth negotiation with supported and unsupported +// methods. +func TestAuthUnsupported2(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + var err error + var method byte + + // VER = 05, NMETHODS = 03, METHODS = [00,01,02] + c.writeHex("0503000102") + if method, err = req.negotiateAuth(); err != nil { + t.Error("negotiateAuth(Unknown2) failed:", err) + } + if method != authUsernamePassword { + t.Error("negotiateAuth(Unknown2) picked unexpected method:", method) + } + if msg := c.readHex(); msg != "0502" { + t.Error("negotiateAuth(Unknown2) invalid response:", msg) + } +} + +// TestRFC1929InvalidVersion tests RFC1929 auth with an invalid version. +func TestRFC1929InvalidVersion(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + + // VER = 03, ULEN = 5, UNAME = "ABCDE", PLEN = 5, PASSWD = "abcde" + c.writeHex("03054142434445056162636465") + if err := req.authenticate(authUsernamePassword); err == nil { + t.Error("authenticate(InvalidVersion) succeded") + } + if msg := c.readHex(); msg != "0101" { + t.Error("authenticate(InvalidVersion) invalid response:", msg) + } +} + +// TestRFC1929InvalidUlen tests RFC1929 auth with an invalid ULEN. +func TestRFC1929InvalidUlen(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + + // VER = 01, ULEN = 0, UNAME = "", PLEN = 5, PASSWD = "abcde" + c.writeHex("0100056162636465") + if err := req.authenticate(authUsernamePassword); err == nil { + t.Error("authenticate(InvalidUlen) succeded") + } + if msg := c.readHex(); msg != "0101" { + t.Error("authenticate(InvalidUlen) invalid response:", msg) + } +} + +// TestRFC1929InvalidPlen tests RFC1929 auth with an invalid PLEN. +func TestRFC1929InvalidPlen(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + + // VER = 01, ULEN = 5, UNAME = "ABCDE", PLEN = 0, PASSWD = "" + c.writeHex("0105414243444500") + if err := req.authenticate(authUsernamePassword); err == nil { + t.Error("authenticate(InvalidPlen) succeded") + } + if msg := c.readHex(); msg != "0101" { + t.Error("authenticate(InvalidPlen) invalid response:", msg) + } +} + +// TestRFC1929InvalidArgs tests RFC1929 auth with invalid pt args. +func TestRFC1929InvalidPTArgs(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + + // VER = 01, ULEN = 5, UNAME = "ABCDE", PLEN = 5, PASSWD = "abcde" + c.writeHex("01054142434445056162636465") + if err := req.authenticate(authUsernamePassword); err == nil { + t.Error("authenticate(InvalidArgs) succeded") + } + if msg := c.readHex(); msg != "0101" { + t.Error("authenticate(InvalidArgs) invalid response:", msg) + } +} + +// TestRFC1929Success tests RFC1929 auth with valid pt args. +func TestRFC1929Success(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + + // VER = 01, ULEN = 9, UNAME = "key=value", PLEN = 1, PASSWD = "\0" + c.writeHex("01096b65793d76616c75650100") + if err := req.authenticate(authUsernamePassword); err != nil { + t.Error("authenticate(Success) failed:", err) + } + if msg := c.readHex(); msg != "0100" { + t.Error("authenticate(Success) invalid response:", msg) + } + v, ok := req.Args.Get("key") + if v != "value" || !ok { + t.Error("RFC1929 k,v parse failure:", v) + } +} + +// TestRequestInvalidHdr tests SOCKS5 requests with invalid VER/CMD/RSV/ATYPE +func TestRequestInvalidHdr(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + + // VER = 03, CMD = 01, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 + c.writeHex("030100017f000001235a") + if err := req.readCommand(); err == nil { + t.Error("readCommand(InvalidVer) succeded") + } + if msg := c.readHex(); msg != "05010001000000000000" { + t.Error("readCommand(InvalidVer) invalid response:", msg) + } + c.reset(req) + + // VER = 05, CMD = 05, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 + c.writeHex("050500017f000001235a") + if err := req.readCommand(); err == nil { + t.Error("readCommand(InvalidCmd) succeded") + } + if msg := c.readHex(); msg != "05070001000000000000" { + t.Error("readCommand(InvalidCmd) invalid response:", msg) + } + c.reset(req) + + // VER = 05, CMD = 01, RSV = 30, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 + c.writeHex("050130017f000001235a") + if err := req.readCommand(); err == nil { + t.Error("readCommand(InvalidRsv) succeded") + } + if msg := c.readHex(); msg != "05010001000000000000" { + t.Error("readCommand(InvalidRsv) invalid response:", msg) + } + c.reset(req) + + // VER = 05, CMD = 01, RSV = 01, ATYPE = 05, DST.ADDR = 127.0.0.1, DST.PORT = 9050 + c.writeHex("050100057f000001235a") + if err := req.readCommand(); err == nil { + t.Error("readCommand(InvalidAtype) succeded") + } + if msg := c.readHex(); msg != "05080001000000000000" { + t.Error("readCommand(InvalidAtype) invalid response:", msg) + } + c.reset(req) +} + +// TestRequestIPv4 tests IPv4 SOCKS5 requests. +func TestRequestIPv4(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + + // VER = 05, CMD = 01, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 + c.writeHex("050100017f000001235a") + if err := req.readCommand(); err != nil { + t.Error("readCommand(IPv4) failed:", err) + } + addr, err := net.ResolveTCPAddr("tcp", req.Target) + if err != nil { + t.Error("net.ResolveTCPAddr failed:", err) + } + if !tcpAddrsEqual(addr, &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 9050}) { + t.Error("Unexpected target:", addr) + } +} + +// TestRequestIPv6 tests IPv4 SOCKS5 requests. +func TestRequestIPv6(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + + // VER = 05, CMD = 01, RSV = 00, ATYPE = 04, DST.ADDR = 0102:0304:0506:0708:090a:0b0c:0d0e:0f10, DST.PORT = 9050 + c.writeHex("050100040102030405060708090a0b0c0d0e0f10235a") + if err := req.readCommand(); err != nil { + t.Error("readCommand(IPv6) failed:", err) + } + addr, err := net.ResolveTCPAddr("tcp", req.Target) + if err != nil { + t.Error("net.ResolveTCPAddr failed:", err) + } + if !tcpAddrsEqual(addr, &net.TCPAddr{IP: net.ParseIP("0102:0304:0506:0708:090a:0b0c:0d0e:0f10"), Port: 9050}) { + t.Error("Unexpected target:", addr) + } +} + +// TestRequestFQDN tests FQDN (DOMAINNAME) SOCKS5 requests. +func TestRequestFQDN(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + + // VER = 05, CMD = 01, RSV = 00, ATYPE = 04, DST.ADDR = example.com, DST.PORT = 9050 + c.writeHex("050100030b6578616d706c652e636f6d235a") + if err := req.readCommand(); err != nil { + t.Error("readCommand(FQDN) failed:", err) + } + if req.Target != "example.com:9050" { + t.Error("Unexpected target:", req.Target) + } +} + +// TestResponseNil tests nil address SOCKS5 responses. +func TestResponseNil(t *testing.T) { + c := new(testReadWriter) + req := c.toRequest() + + if err := req.Reply(ReplySucceeded); err != nil { + t.Error("Reply(ReplySucceeded) failed:", err) + } + if msg := c.readHex(); msg != "05000001000000000000" { + t.Error("Reply(ReplySucceeded) invalid response:", msg) + } +} + +var _ io.ReadWriter = (*testReadWriter)(nil) |