386 socks.go
@@ -9,11 +9,40 @@ import (
)

const (
socksVersion = 0x04
socksCmdConnect = 0x01
socksResponseVersion = 0x00
socksRequestGranted = 0x5a
socksRequestRejected = 0x5b
socksVersion = 0x05

socksAuthNoneRequired = 0x00
socksAuthUsernamePassword = 0x02
socksAuthNoAcceptableMethods = 0xff

socksCmdConnect = 0x01
socksRsv = 0x00

socksAtypeV4 = 0x01
socksAtypeDomainName = 0x03
socksAtypeV6 = 0x04

socksAuthRFC1929Ver = 0x01
socksAuthRFC1929Success = 0x00
socksAuthRFC1929Fail = 0x01

socksRepSucceeded = 0x00
// "general SOCKS server failure"
SocksRepGeneralFailure = 0x01
// "connection not allowed by ruleset"
SocksRepConnectionNotAllowed = 0x02
// "Network unreachable"
SocksRepNetworkUnreachable = 0x03
// "Host unreachable"
SocksRepHostUnreachable = 0x04
// "Connection refused"
SocksRepConnectionRefused = 0x05
// "TTL expired"
SocksRepTTLExpired = 0x06
// "Command not supported"
SocksRepCommandNotSupported = 0x07
// "Address type not supported"
SocksRepAddressNotSupported = 0x08
)

// Put a sanity timeout on how long we wait for a SOCKS request.
@@ -25,6 +54,8 @@ type SocksRequest struct {
Target string
// The userid string sent by the client.
Username string
// The password string sent by the client.
Password string
// The parsed contents of Username as a key–value mapping.
Args Args
}
@@ -36,15 +67,23 @@ type SocksConn struct {
}

// Send a message to the proxy client that access to the given address is
// granted. If the IP field inside addr is not an IPv4 address, the IP portion
// of the response will be four zero bytes.
// granted. Addr is ignored, and "0.0.0.0:0" is always sent back for
// BND.ADDR/BND.PORT in the SOCKS response.
func (conn *SocksConn) Grant(addr *net.TCPAddr) error {
return sendSocks4aResponseGranted(conn, addr)
return sendSocks5ResponseGranted(conn)
}

// Send a message to the proxy client that access was rejected or failed.
// Send a message to the proxy client that access was rejected or failed. This
// sends back a "General Failure" error code. RejectReason should be used if
// more specific error reporting is desired.
func (conn *SocksConn) Reject() error {
return sendSocks4aResponseRejected(conn)
return conn.RejectReason(SocksRepGeneralFailure)
}

// Send a message to the proxy client that access was rejected, with the
// specific error code indicating the reason behind the rejection.
func (conn *SocksConn) RejectReason(reason byte) error {
return sendSocks5ResponseRejected(conn, reason)
}

// SocksListener wraps a net.Listener in order to read a SOCKS request on Accept.
@@ -138,7 +177,7 @@ func (ln *SocksListener) AcceptSocks() (*SocksConn, error) {
if err != nil {
return nil, err
}
conn.Req, err = readSocks4aConnect(conn)
conn.Req, err = socks5Handshake(conn)
if err != nil {
conn.Close()
return nil, err
@@ -150,93 +189,316 @@ func (ln *SocksListener) AcceptSocks() (*SocksConn, error) {
return conn, nil
}

// Returns "socks4", suitable to be included in a call to Cmethod.
// Returns "socks5", suitable to be included in a call to Cmethod.
func (ln *SocksListener) Version() string {
return "socks4"
return "socks5"
}

// Read a SOCKS4a connect request. Returns a SocksRequest.
func readSocks4aConnect(s io.Reader) (req SocksRequest, err error) {
r := bufio.NewReader(s)
// socks5handshake conducts the SOCKS5 handshake up to the point where the
// client command is read and the proxy must open the outgoing connection.
// Returns a SocksRequest.
func socks5Handshake(s io.ReadWriter) (req SocksRequest, err error) {
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))

var h [8]byte
_, err = io.ReadFull(r, h[:])
if err != nil {
// Negotiate the authentication method.
var method byte
if method, err = socksNegotiateAuth(rw); err != nil {
return
}
if h[0] != socksVersion {
err = fmt.Errorf("SOCKS header had version 0x%02x, not 0x%02x", h[0], socksVersion)

// Authenticate the client.
if err = socksAuthenticate(rw, method, &req); err != nil {
return
}
if h[1] != socksCmdConnect {
err = fmt.Errorf("SOCKS header had command 0x%02x, not 0x%02x", h[1], socksCmdConnect)

// Read the command.
err = socksReadCommand(rw, &req)
return
}

// socksNegotiateAuth negotiates the authentication method and returns the
// selected method as a byte. On negotiation failures an error is returned.
func socksNegotiateAuth(rw *bufio.ReadWriter) (method byte, err error) {
// Validate the version.
if err = socksReadByteVerify(rw, "version", socksVersion); err != nil {
return
}

var usernameBytes []byte
usernameBytes, err = r.ReadBytes('\x00')
if err != nil {
// Read the number of methods.
var nmethods byte
if nmethods, err = socksReadByte(rw); err != nil {
return
}
req.Username = string(usernameBytes[:len(usernameBytes)-1])

req.Args, err = parseClientParameters(req.Username)
if err != nil {
// Read the methods.
var methods []byte
if methods, err = socksReadBytes(rw, int(nmethods)); err != nil {
return
}

var port int
var host string
// Pick the most "suitable" method.
method = socksAuthNoAcceptableMethods
for _, m := range methods {
switch m {
case socksAuthNoneRequired:
// Pick Username/Password over None if the client happens to
// send both.
if method == socksAuthNoAcceptableMethods {
method = m
}

case socksAuthUsernamePassword:
method = m
}
}

// Send the negotiated method.
var msg [2]byte
msg[0] = socksVersion
msg[1] = method
if _, err = rw.Writer.Write(msg[:]); err != nil {
return
}

if err = socksFlushBuffers(rw); err != nil {
return
}
return
}

// socksAuthenticate authenticates the client via the chosen authentication
// mechanism.
func socksAuthenticate(rw *bufio.ReadWriter, method byte, req *SocksRequest) (err error) {
switch method {
case socksAuthNoneRequired:
// Straight into reading the connect.

port = int(h[2])<<8 | int(h[3])<<0
if h[4] == 0 && h[5] == 0 && h[6] == 0 && h[7] != 0 {
var hostBytes []byte
hostBytes, err = r.ReadBytes('\x00')
if err != nil {
case socksAuthUsernamePassword:
if err = socksAuthRFC1929(rw, req); err != nil {
return
}
host = string(hostBytes[:len(hostBytes)-1])

case socksAuthNoAcceptableMethods:
err = fmt.Errorf("SOCKS method select had no compatible methods")
return

default:
err = fmt.Errorf("SOCKS method select picked a unsupported method 0x%02x", method)
return
}

if err = socksFlushBuffers(rw); err != nil {
return
}
return
}

// socksAuthRFC1929 authenticates the client via RFC 1929 username/password
// auth. As a design decision any valid username/password is accepted as this
// field is primarily used as an out-of-band argument passing mechanism for
// pluggable transports.
func socksAuthRFC1929(rw *bufio.ReadWriter, req *SocksRequest) (err error) {
sendErrResp := func() {
// Swallow the write/flush error here, we are going to close the
// connection and the original failure is more useful.
resp := []byte{socksAuthRFC1929Ver, socksAuthRFC1929Fail}
rw.Write(resp[:])
socksFlushBuffers(rw)
}

// Validate the fixed parts of the command message.
if err = socksReadByteVerify(rw, "auth version", socksAuthRFC1929Ver); err != nil {
sendErrResp()
return
}

// Read the username.
var ulen byte
if ulen, err = socksReadByte(rw); err != nil {
return
}
if ulen < 1 {
sendErrResp()
err = fmt.Errorf("RFC1929 username with 0 length")
return
}
var uname []byte
if uname, err = socksReadBytes(rw, int(ulen)); err != nil {
return
}
req.Username = string(uname)

// Read the password.
var plen byte
if plen, err = socksReadByte(rw); err != nil {
return
}
if plen < 1 {
sendErrResp()
err = fmt.Errorf("RFC1929 password with 0 length")
return
}
var passwd []byte
if passwd, err = socksReadBytes(rw, int(plen)); err != nil {
return
}
if !(plen == 1 && passwd[0] == 0x00) {
// tor will set the password to 'NUL' if there are no arguments.
req.Password = string(passwd)
}

// Mash the username/password together and parse it as a pluggable
// transport argument string.
if req.Args, err = parseClientParameters(req.Username + req.Password); err != nil {
sendErrResp()
} else {
host = net.IPv4(h[4], h[5], h[6], h[7]).String()
resp := []byte{socksAuthRFC1929Ver, socksAuthRFC1929Success}
_, err = rw.Write(resp[:])
}
return
}

// socksReadCommand reads a SOCKS5 client command and parses out the relevant
// fields into a SocksRequest. Only CMD_CONNECT is supported.
func socksReadCommand(rw *bufio.ReadWriter, req *SocksRequest) (err error) {
sendErrResp := func(reason byte) {
// Swallow errors that occur when writing/flushing the response,
// connection will be closed anyway.
sendSocks5ResponseRejected(rw, reason)
socksFlushBuffers(rw)
}

// Validate the fixed parts of the command message.
if err = socksReadByteVerify(rw, "version", socksVersion); err != nil {
sendErrResp(SocksRepGeneralFailure)
return
}
if err = socksReadByteVerify(rw, "command", socksCmdConnect); err != nil {
sendErrResp(SocksRepCommandNotSupported)
return
}
if err = socksReadByteVerify(rw, "reserved", socksRsv); err != nil {
sendErrResp(SocksRepGeneralFailure)
return
}

// Read the destination address/port.
// XXX: This should probably eventually send socks 5 error messages instead
// of rudely closing connections on invalid addresses.
var atype byte
if atype, err = socksReadByte(rw); err != nil {
return
}
var host string
switch atype {
case socksAtypeV4:
var addr []byte
if addr, err = socksReadBytes(rw, net.IPv4len); err != nil {
return
}
host = net.IPv4(addr[0], addr[1], addr[2], addr[3]).String()

case socksAtypeDomainName:
var alen byte
if alen, err = socksReadByte(rw); err != nil {
return
}
if alen == 0 {
err = fmt.Errorf("SOCKS request had domain name with 0 length")
return
}
var addr []byte
if addr, err = socksReadBytes(rw, int(alen)); err != nil {
return
}
host = string(addr)

case socksAtypeV6:
var rawAddr []byte
if rawAddr, err = socksReadBytes(rw, net.IPv6len); err != nil {
return
}
addr := make(net.IP, net.IPv6len)
copy(addr[:], rawAddr[:])
host = fmt.Sprintf("[%s]", addr.String())

default:
sendErrResp(SocksRepAddressNotSupported)
err = fmt.Errorf("SOCKS request had unsupported address type 0x%02x", atype)
return
}
var rawPort []byte
if rawPort, err = socksReadBytes(rw, 2); err != nil {
return
}
port := int(rawPort[0])<<8 | int(rawPort[1])<<0

if r.Buffered() != 0 {
err = fmt.Errorf("%d bytes left after SOCKS header", r.Buffered())
if err = socksFlushBuffers(rw); err != nil {
return
}

req.Target = fmt.Sprintf("%s:%d", host, port)
return
}

// Send a SOCKS4a response with the given code and address. If the IP field
// inside addr is not an IPv4 address, the IP portion of the response will be
// four zero bytes.
func sendSocks4aResponse(w io.Writer, code byte, addr *net.TCPAddr) error {
var resp [8]byte
resp[0] = socksResponseVersion
// Send a SOCKS5 response with the given code. BND.ADDR/BND.PORT is always the
// IPv4 address/port "0.0.0.0:0".
func sendSocks5Response(w io.Writer, code byte) error {
resp := make([]byte, 4+4+2)
resp[0] = socksVersion
resp[1] = code
resp[2] = byte((addr.Port >> 8) & 0xff)
resp[3] = byte((addr.Port >> 0) & 0xff)
ipv4 := addr.IP.To4()
if ipv4 != nil {
resp[4] = ipv4[0]
resp[5] = ipv4[1]
resp[6] = ipv4[2]
resp[7] = ipv4[3]
}
resp[2] = socksRsv
resp[3] = socksAtypeV4

// BND.ADDR/BND.PORT should be the address and port that the outgoing
// connection is bound to on the proxy, but Tor does not use this
// information, so all zeroes are sent.

_, err := w.Write(resp[:])
return err
}

var emptyAddr = net.TCPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 0}
// Send a SOCKS5 response code 0x00.
func sendSocks5ResponseGranted(w io.Writer) error {
return sendSocks5Response(w, socksRepSucceeded)
}

// Send a SOCKS4a response code 0x5a.
func sendSocks4aResponseGranted(w io.Writer, addr *net.TCPAddr) error {
return sendSocks4aResponse(w, socksRequestGranted, addr)
// Send a SOCKS5 response with the provided failure reason.
func sendSocks5ResponseRejected(w io.Writer, reason byte) error {
return sendSocks5Response(w, reason)
}

// Send a SOCKS4a response code 0x5b (with an all-zero address).
func sendSocks4aResponseRejected(w io.Writer) error {
return sendSocks4aResponse(w, socksRequestRejected, &emptyAddr)
func socksFlushBuffers(rw *bufio.ReadWriter) error {
if err := rw.Writer.Flush(); err != nil {
return err
}
if rw.Reader.Buffered() > 0 {
return fmt.Errorf("%d bytes left after SOCKS message", rw.Reader.Buffered())
}
return nil
}

func socksReadByte(rw *bufio.ReadWriter) (byte, error) {
return rw.Reader.ReadByte()
}

func socksReadBytes(rw *bufio.ReadWriter, n int) ([]byte, error) {
ret := make([]byte, n)
if _, err := io.ReadFull(rw.Reader, ret); err != nil {
return nil, err
}
return ret, nil
}

func socksReadByteVerify(rw *bufio.ReadWriter, descr string, expected byte) error {
val, err := socksReadByte(rw)
if err != nil {
return err
}
if val != expected {
return fmt.Errorf("SOCKS message field %s was 0x%02x, not 0x%02x", descr, val, expected)
}
return nil
}

var _ net.Listener = (*SocksListener)(nil)
@@ -1,162 +1,368 @@
package pt

import (
"bufio"
"bytes"
"encoding/hex"
"io"
"net"
"testing"
)

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)
}
}

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)
}
}

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)
}
}
}

func TestSendSocks4aResponse(t *testing.T) {
tests := [...]struct {
code byte
addr net.TCPAddr
expected []byte
}{
{
socksRequestGranted,
net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 0x1234},
[]byte("\x00\x5a\x12\x34\x01\x02\x03\x04"),
},
{
socksRequestRejected,
net.TCPAddr{IP: net.ParseIP("1:2::3:4"), Port: 0x1234},
[]byte("\x00\x5b\x12\x34\x00\x00\x00\x00"),
},
}

for _, test := range tests {
var buf bytes.Buffer
err := sendSocks4aResponse(&buf, test.code, &test.addr)
if err != nil {
t.Errorf("0x%02x %s unexpectedly returned an error: %s", test.code, &test.addr, err)
}
p := make([]byte, 1024)
n, err := buf.Read(p)
if err != nil {
t.Fatal(err)
}
output := p[:n]
if !bytes.Equal(output, test.expected) {
t.Errorf("0x%02x %s → %v (expected %v)",
test.code, &test.addr, output, test.expected)
}
// 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) 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")
}
}

// 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)
}
}

// TestAuthNoneRequired tests auth negotiaton with NO AUTHENTICATION REQUIRED.
func TestAuthNoneRequired(t *testing.T) {
c := new(testReadWriter)
var err error
var method byte

// VER = 05, NMETHODS = 01, METHODS = [00]
c.writeHex("050100")
if method, err = socksNegotiateAuth(c.toBufio()); err != nil {
t.Error("socksNegotiateAuth(None) failed:", err)
}
if method != socksAuthNoneRequired {
t.Error("socksNegotiateAuth(None) unexpected method:", method)
}
if msg := c.readHex(); msg != "0500" {
t.Error("socksNegotiateAuth(None) invalid response:", msg)
}
}

// TestAuthUsernamePassword tests auth negotiation with USERNAME/PASSWORD.
func TestAuthUsernamePassword(t *testing.T) {
c := new(testReadWriter)
var err error
var method byte

// VER = 05, NMETHODS = 01, METHODS = [02]
c.writeHex("050102")
if method, err = socksNegotiateAuth(c.toBufio()); err != nil {
t.Error("socksNegotiateAuth(UsernamePassword) failed:", err)
}
if method != socksAuthUsernamePassword {
t.Error("socksNegotiateAuth(UsernamePassword) unexpected method:", method)
}
if msg := c.readHex(); msg != "0502" {
t.Error("socksNegotiateAuth(UsernamePassword) invalid response:", msg)
}
}

// 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)