commit 15d8df1cd780ddb0bb1710b08a5546f2a8f027bf Merge: f17a5f2 93bcaf4 Author: David Fifield david@bamsoftware.com Date: Sun May 1 23:29:42 2016 -0700
Merge remote-tracking branch 'yawning/bug12535_v2'
pt.go | 5 +- socks.go | 386 +++++++++++++++++++++++++++++++++++++-------- socks_test.go | 495 +++++++++++++++++++++++++++++++++++++++++----------------- 3 files changed, 677 insertions(+), 209 deletions(-)
diff --cc pt.go index 28e5bb7,45cf67f..82c42d5 --- a/pt.go +++ b/pt.go @@@ -125,13 -119,11 +125,14 @@@ // Extended ORPort Authentication: // https://gitweb.torproject.org/torspec.git/tree/proposals/217-ext-orport-auth.... // +// Pluggable Transport through SOCKS proxy: +// https://gitweb.torproject.org/torspec.git/tree/proposals/232-pluggable-trans... +// - // The package implements a SOCKS4a server sufficient for a Tor client transport + // The package implements a SOCKS5 server sufficient for a Tor client transport // plugin. // - // http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol + // https://www.ietf.org/rfc/rfc1928.txt + // https://www.ietf.org/rfc/rfc1929.txt package pt
import ( diff --cc socks.go index 9a764b8,e4488e8..29827d9 --- a/socks.go +++ b/socks.go @@@ -137,13 -175,12 +176,13 @@@ retry conn.Conn = c err = conn.SetDeadline(time.Now().Add(socksRequestTimeout)) if err != nil { - return nil, err + conn.Close() + goto retry } - conn.Req, err = readSocks4aConnect(conn) + conn.Req, err = socks5Handshake(conn) if err != nil { conn.Close() - return nil, err + goto retry } err = conn.SetDeadline(time.Time{}) if err != nil { diff --cc socks_test.go index 7fee46a,aa27d4c..d82e823 --- a/socks_test.go +++ b/socks_test.go @@@ -1,129 -1,78 +1,80 @@@ package pt
import ( + "bufio" "bytes" + "errors" + "encoding/hex" "io" "net" "testing" + "time" )
- func TestReadSocks4aConnect(t *testing.T) { - badTests := [...][]byte{ - []byte(""), - // missing userid - []byte("\x04\x01\x12\x34\x01\x02\x03\x04"), - // missing \x00 after userid - []byte("\x04\x01\x12\x34\x01\x02\x03\x04key=value"), - // missing hostname - []byte("\x04\x01\x12\x34\x00\x00\x00\x01key=value\x00"), - // missing \x00 after hostname - []byte("\x04\x01\x12\x34\x00\x00\x00\x01key=value\x00hostname"), - // bad name–value mapping - []byte("\x04\x01\x12\x34\x00\x00\x00\x01userid\x00hostname\x00"), - // bad version number - []byte("\x03\x01\x12\x34\x01\x02\x03\x04\x00"), - // BIND request - []byte("\x04\x02\x12\x34\x01\x02\x03\x04\x00"), - // SOCKS5 - []byte("\x05\x01\x00"), - } - ipTests := [...]struct { - input []byte - addr net.TCPAddr - userid string - }{ - { - []byte("\x04\x01\x12\x34\x01\x02\x03\x04key=value\x00"), - net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 0x1234}, - "key=value", - }, - { - []byte("\x04\x01\x12\x34\x01\x02\x03\x04\x00"), - net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 0x1234}, - "", - }, - } - hostnameTests := [...]struct { - input []byte - target string - userid string - }{ - { - []byte("\x04\x01\x12\x34\x00\x00\x00\x01key=value\x00hostname\x00"), - "hostname:4660", - "key=value", - }, - { - []byte("\x04\x01\x12\x34\x00\x00\x00\x01\x00hostname\x00"), - "hostname:4660", - "", - }, - { - []byte("\x04\x01\x12\x34\x00\x00\x00\x01key=value\x00\x00"), - ":4660", - "key=value", - }, - { - []byte("\x04\x01\x12\x34\x00\x00\x00\x01\x00\x00"), - ":4660", - "", - }, - } - - for _, input := range badTests { - var buf bytes.Buffer - buf.Write(input) - _, err := readSocks4aConnect(&buf) - if err == nil { - t.Errorf("%q unexpectedly succeeded", input) - } + // 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) + }
- for _, test := range ipTests { - var buf bytes.Buffer - buf.Write(test.input) - req, err := readSocks4aConnect(&buf) - if err != nil { - t.Errorf("%q unexpectedly returned an error: %s", test.input, err) - } - addr, err := net.ResolveTCPAddr("tcp", req.Target) - if err != nil { - t.Errorf("%q → target %q: cannot resolve: %s", test.input, - req.Target, err) - } - if !tcpAddrsEqual(addr, &test.addr) { - t.Errorf("%q → address %s (expected %s)", test.input, - req.Target, test.addr.String()) - } - if req.Username != test.userid { - t.Errorf("%q → username %q (expected %q)", test.input, - req.Username, test.userid) - } - if req.Args == nil { - t.Errorf("%q → unexpected nil Args from username %q", test.input, req.Username) - } + 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) reset() { + c.readBuf.Reset() + c.writeBuf.Reset() + } + + // TestAuthInvalidVersion tests auth negotiation with an invalid version. + func TestAuthInvalidVersion(t *testing.T) { + c := new(testReadWriter) + + // VER = 03, NMETHODS = 01, METHODS = [00] + c.writeHex("030100") + if _, err := socksNegotiateAuth(c.toBufio()); err == nil { + t.Error("socksNegotiateAuth(InvalidVersion) succeded") } + }
- for _, test := range hostnameTests { - var buf bytes.Buffer - buf.Write(test.input) - req, err := readSocks4aConnect(&buf) - if err != nil { - t.Errorf("%q unexpectedly returned an error: %s", test.input, err) - } - if req.Target != test.target { - t.Errorf("%q → target %q (expected %q)", test.input, - req.Target, test.target) - } - if req.Username != test.userid { - t.Errorf("%q → username %q (expected %q)", test.input, - req.Username, test.userid) - } - if req.Args == nil { - t.Errorf("%q → unexpected nil Args from username %q", test.input, req.Username) - } + // TestAuthInvalidNMethods tests auth negotiaton with no methods. + func TestAuthInvalidNMethods(t *testing.T) { + c := new(testReadWriter) + var err error + var method byte + + // VER = 05, NMETHODS = 00 + c.writeHex("0500") + if method, err = socksNegotiateAuth(c.toBufio()); err != nil { + t.Error("socksNegotiateAuth(No Methods) failed:", err) + } + if method != socksAuthNoAcceptableMethods { + t.Error("socksNegotiateAuth(No Methods) picked unexpected method:", method) + } + if msg := c.readHex(); msg != "05ff" { + t.Error("socksNegotiateAuth(No Methods) invalid response:", msg) } }
@@@ -164,106 -114,255 +116,359 @@@ func TestAuthUsernamePassword(t *testin } }
+var fakeListenerDistinguishedError = errors.New("distinguished error") + +// fakeListener is a fake dummy net.Listener that returns the given net.Conn and +// error the first time Accept is called. After the first call, it returns +// (nil, fakeListenerDistinguishedError). +type fakeListener struct { + c net.Conn + err error +} + +func (ln *fakeListener) Accept() (net.Conn, error) { + c := ln.c + err := ln.err + ln.c = nil + ln.err = fakeListenerDistinguishedError + return c, err +} + +func (ln *fakeListener) Close() error { + return nil +} + +func (ln *fakeListener) Addr() net.Addr { + return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0, Zone: ""} +} + +// A trivial net.Error that lets you control whether it is considered Temporary. +type netError struct { + errString string + temporary bool +} + +func (e *netError) Error() string { + return e.errString +} + +func (e *netError) Temporary() bool { + return e.temporary +} + +func (e *netError) Timeout() bool { + return false +} + +// The purpose of ignoreDeadlineConn is to wrap net.Pipe so that the deadline +// functions don't return an error ("net.Pipe does not support deadlines"). +type ignoreDeadlineConn struct { + net.Conn +} + +func (c *ignoreDeadlineConn) SetDeadline(t time.Time) error { + return nil +} + +func (c *ignoreDeadlineConn) SetReadDeadline(t time.Time) error { + return nil +} + +func (c *ignoreDeadlineConn) SetWriteDeadline(t time.Time) error { + return nil +} + +func TestAcceptErrors(t *testing.T) { + // Check that AcceptSocks accurately reflects net.Errors returned by the + // underlying call to Accept. This is important for the handling of + // Temporary and non-Temporary errors. The loop iterates over + // non-net.Error, non-Temporary net.Error, and Temporary net.Error. + for _, expectedErr := range []error{io.EOF, &netError{"non-temp", false}, &netError{"temp", true}} { + ln := NewSocksListener(&fakeListener{nil, expectedErr}) + _, err := ln.AcceptSocks() + if expectedNerr, ok := expectedErr.(net.Error); ok { + nerr, ok := err.(net.Error) + if !ok { + t.Errorf("AcceptSocks returned non-net.Error %v", nerr) + } else { + if expectedNerr.Temporary() != expectedNerr.Temporary() { + t.Errorf("AcceptSocks did not keep Temporary status of net.Error: %v", nerr) + } + } + } + } + + c1, c2 := net.Pipe() + go func() { + // Bogus request: SOCKS 5 then EOF. + c2.Write([]byte("\x05\x01\x00")) + c2.Close() + }() + ln := NewSocksListener(&fakeListener{c: &ignoreDeadlineConn{c1}, err: nil}) + _, err := ln.AcceptSocks() + // The error in parsing the SOCKS request must be either silently + // ignored, or else must be a Temporary net.Error. I.e., it must not be + // the io.ErrUnexpectedEOF caused by the short request. + if err == fakeListenerDistinguishedError { + // Was silently ignored. + } else if nerr, ok := err.(net.Error); ok { + if !nerr.Temporary() { + t.Errorf("AcceptSocks returned non-Temporary net.Error: %v", nerr) + } + } else { + t.Errorf("AcceptSocks returned non-net.Error: %v", err) + } +} ++ + // TestAuthBoth tests auth negotiation containing both NO AUTHENTICATION + // REQUIRED and USERNAME/PASSWORD. + func TestAuthBoth(t *testing.T) { + c := new(testReadWriter) + var err error + var method byte + + // VER = 05, NMETHODS = 02, METHODS = [00, 02] + c.writeHex("05020002") + if method, err = socksNegotiateAuth(c.toBufio()); err != nil { + t.Error("socksNegotiateAuth(Both) failed:", err) + } + if method != socksAuthUsernamePassword { + t.Error("socksNegotiateAuth(Both) unexpected method:", method) + } + if msg := c.readHex(); msg != "0502" { + t.Error("socksNegotiateAuth(Both) invalid response:", msg) + } + } + + // TestAuthUnsupported tests auth negotiation with a unsupported method. + func TestAuthUnsupported(t *testing.T) { + c := new(testReadWriter) + var err error + var method byte + + // VER = 05, NMETHODS = 01, METHODS = [01] (GSSAPI) + c.writeHex("050101") + if method, err = socksNegotiateAuth(c.toBufio()); err != nil { + t.Error("socksNegotiateAuth(Unknown) failed:", err) + } + if method != socksAuthNoAcceptableMethods { + t.Error("socksNegotiateAuth(Unknown) picked unexpected method:", method) + } + if msg := c.readHex(); msg != "05ff" { + t.Error("socksNegotiateAuth(Unknown) invalid response:", msg) + } + } + + // TestAuthUnsupported2 tests auth negotiation with supported and unsupported + // methods. + func TestAuthUnsupported2(t *testing.T) { + c := new(testReadWriter) + var err error + var method byte + + // VER = 05, NMETHODS = 03, METHODS = [00,01,02] + c.writeHex("0503000102") + if method, err = socksNegotiateAuth(c.toBufio()); err != nil { + t.Error("socksNegotiateAuth(Unknown2) failed:", err) + } + if method != socksAuthUsernamePassword { + t.Error("socksNegotiateAuth(Unknown2) picked unexpected method:", method) + } + if msg := c.readHex(); msg != "0502" { + t.Error("socksNegotiateAuth(Unknown2) invalid response:", msg) + } + } + + // TestRFC1929InvalidVersion tests RFC1929 auth with an invalid version. + func TestRFC1929InvalidVersion(t *testing.T) { + c := new(testReadWriter) + var req SocksRequest + + // VER = 03, ULEN = 5, UNAME = "ABCDE", PLEN = 5, PASSWD = "abcde" + c.writeHex("03054142434445056162636465") + if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { + t.Error("socksAuthenticate(InvalidVersion) succeded") + } + if msg := c.readHex(); msg != "0101" { + t.Error("socksAuthenticate(InvalidVersion) invalid response:", msg) + } + } + + // TestRFC1929InvalidUlen tests RFC1929 auth with an invalid ULEN. + func TestRFC1929InvalidUlen(t *testing.T) { + c := new(testReadWriter) + var req SocksRequest + + // VER = 01, ULEN = 0, UNAME = "", PLEN = 5, PASSWD = "abcde" + c.writeHex("0100056162636465") + if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { + t.Error("socksAuthenticate(InvalidUlen) succeded") + } + if msg := c.readHex(); msg != "0101" { + t.Error("socksAuthenticate(InvalidUlen) invalid response:", msg) + } + } + + // TestRFC1929InvalidPlen tests RFC1929 auth with an invalid PLEN. + func TestRFC1929InvalidPlen(t *testing.T) { + c := new(testReadWriter) + var req SocksRequest + + // VER = 01, ULEN = 5, UNAME = "ABCDE", PLEN = 0, PASSWD = "" + c.writeHex("0105414243444500") + if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { + t.Error("socksAuthenticate(InvalidPlen) succeded") + } + if msg := c.readHex(); msg != "0101" { + t.Error("socksAuthenticate(InvalidPlen) invalid response:", msg) + } + } + + // TestRFC1929InvalidArgs tests RFC1929 auth with invalid pt args. + func TestRFC1929InvalidPTArgs(t *testing.T) { + c := new(testReadWriter) + var req SocksRequest + + // VER = 01, ULEN = 5, UNAME = "ABCDE", PLEN = 5, PASSWD = "abcde" + c.writeHex("01054142434445056162636465") + if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { + t.Error("socksAuthenticate(InvalidArgs) succeded") + } + if msg := c.readHex(); msg != "0101" { + t.Error("socksAuthenticate(InvalidArgs) invalid response:", msg) + } + } + + // TestRFC1929Success tests RFC1929 auth with valid pt args. + func TestRFC1929Success(t *testing.T) { + c := new(testReadWriter) + var req SocksRequest + + // VER = 01, ULEN = 9, UNAME = "key=value", PLEN = 1, PASSWD = "\0" + c.writeHex("01096b65793d76616c75650100") + if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err != nil { + t.Error("socksAuthenticate(Success) failed:", err) + } + if msg := c.readHex(); msg != "0100" { + t.Error("socksAuthenticate(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) + var req SocksRequest + + // VER = 03, CMD = 01, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 + c.writeHex("030100017f000001235a") + if err := socksReadCommand(c.toBufio(), &req); err == nil { + t.Error("socksReadCommand(InvalidVer) succeded") + } + if msg := c.readHex(); msg != "05010001000000000000" { + t.Error("socksReadCommand(InvalidVer) invalid response:", msg) + } + c.reset() + + // VER = 05, CMD = 05, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 + c.writeHex("050500017f000001235a") + if err := socksReadCommand(c.toBufio(), &req); err == nil { + t.Error("socksReadCommand(InvalidCmd) succeded") + } + if msg := c.readHex(); msg != "05070001000000000000" { + t.Error("socksReadCommand(InvalidCmd) invalid response:", msg) + } + c.reset() + + // VER = 05, CMD = 01, RSV = 30, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 + c.writeHex("050130017f000001235a") + if err := socksReadCommand(c.toBufio(), &req); err == nil { + t.Error("socksReadCommand(InvalidRsv) succeded") + } + if msg := c.readHex(); msg != "05010001000000000000" { + t.Error("socksReadCommand(InvalidRsv) invalid response:", msg) + } + c.reset() + + // VER = 05, CMD = 01, RSV = 01, ATYPE = 05, DST.ADDR = 127.0.0.1, DST.PORT = 9050 + c.writeHex("050100057f000001235a") + if err := socksReadCommand(c.toBufio(), &req); err == nil { + t.Error("socksReadCommand(InvalidAtype) succeded") + } + if msg := c.readHex(); msg != "05080001000000000000" { + t.Error("socksAuthenticate(InvalidAtype) invalid response:", msg) + } + c.reset() + } + + // TestRequestIPv4 tests IPv4 SOCKS5 requests. + func TestRequestIPv4(t *testing.T) { + c := new(testReadWriter) + var req SocksRequest + + // VER = 05, CMD = 01, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 + c.writeHex("050100017f000001235a") + if err := socksReadCommand(c.toBufio(), &req); err != nil { + t.Error("socksReadCommand(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) + var req SocksRequest + + // 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 := socksReadCommand(c.toBufio(), &req); err != nil { + t.Error("socksReadCommand(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) + var req SocksRequest + + // VER = 05, CMD = 01, RSV = 00, ATYPE = 04, DST.ADDR = example.com, DST.PORT = 9050 + c.writeHex("050100030b6578616d706c652e636f6d235a") + if err := socksReadCommand(c.toBufio(), &req); err != nil { + t.Error("socksReadCommand(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) + + b := c.toBufio() + if err := sendSocks5ResponseGranted(b); err != nil { + t.Error("sendSocks5ResponseGranted() failed:", err) + } + b.Flush() + if msg := c.readHex(); msg != "05000001000000000000" { + t.Error("sendSocks5ResponseGranted(nil) invalid response:", msg) + } + } + + var _ io.ReadWriter = (*testReadWriter)(nil)
tor-commits@lists.torproject.org