commit 37be077e66cbff6403e054b2351e2eab3037a292 Author: David Fifield david@bamsoftware.com Date: Sat Dec 7 20:50:27 2013 -0800
Move socks code into the pt package. --- examples/dummy-client/dummy-client.go | 7 +- pt.go | 9 +- socks.go | 189 +++++++++++++++++++++++++++++++++ socks/socks.go | 183 ------------------------------- socks/socks_test.go | 113 -------------------- socks_test.go | 109 +++++++++++++++++++ 6 files changed, 308 insertions(+), 302 deletions(-)
diff --git a/examples/dummy-client/dummy-client.go b/examples/dummy-client/dummy-client.go index 10f7b8b..de56d99 100644 --- a/examples/dummy-client/dummy-client.go +++ b/examples/dummy-client/dummy-client.go @@ -20,7 +20,6 @@ import ( )
import "git.torproject.org/pluggable-transports/goptlib.git" -import "git.torproject.org/pluggable-transports/goptlib.git/socks"
var ptInfo pt.ClientInfo
@@ -44,7 +43,7 @@ func copyLoop(a, b net.Conn) { wg.Wait() }
-func handleConnection(local *socks.Conn) error { +func handleConnection(local *pt.SocksConn) error { defer local.Close()
handlerChan <- 1 @@ -68,7 +67,7 @@ func handleConnection(local *socks.Conn) error { return nil }
-func acceptLoop(ln *socks.Listener) error { +func acceptLoop(ln *pt.SocksListener) error { for { conn, err := ln.AcceptSocks() if err != nil { @@ -80,7 +79,7 @@ func acceptLoop(ln *socks.Listener) error { }
func startListener(addr string) (net.Listener, error) { - ln, err := socks.Listen("tcp", addr) + ln, err := pt.ListenSocks("tcp", addr) if err != nil { return nil, err } diff --git a/pt.go b/pt.go index 86632d9..ddecc29 100644 --- a/pt.go +++ b/pt.go @@ -8,15 +8,15 @@ // os.Exit(1) // } // for _, methodName := range ptInfo.MethodNames { -// ln, err := startSocksListener() +// ln, err := pt.ListenSocks("tcp", "127.0.0.1:0") // if err != nil { // pt.CmethodError(methodName, err.Error()) // continue // } +// go acceptLoop(ln) // pt.Cmethod(methodName, "socks4", ln.Addr()) // } // pt.CmethodsDone() -// See the socks package for help with writing a SOCKS listener. // // Sample server usage: // func handler(conn net.Conn) { @@ -48,6 +48,11 @@ // // Extended ORPort Authentication: // https://gitweb.torproject.org/torspec.git/blob/HEAD:/proposals/217-ext-orpor.... +// +// The package implements a SOCKS4a server sufficient for a Tor client transport +// plugin. +// +// http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol package pt
import ( diff --git a/socks.go b/socks.go new file mode 100644 index 0000000..a5889fa --- /dev/null +++ b/socks.go @@ -0,0 +1,189 @@ +package pt + +import ( + "bufio" + "errors" + "fmt" + "io" + "net" +) + +const ( + socksVersion = 0x04 + socksCmdConnect = 0x01 + socksResponseVersion = 0x00 + socksRequestGranted = 0x5a + socksRequestRejected = 0x5b +) + +// SocksRequest describes a SOCKS request. +type SocksRequest struct { + Username string + Target string +} + +// SocksConn encapsulates a net.Conn and information associated with a SOCKS request. +type SocksConn struct { + net.Conn + Req SocksRequest +} + +// Send a message to the proxy client that access to the given address is +// granted. +func (conn *SocksConn) Grant(addr *net.TCPAddr) error { + return sendSocks4aResponseGranted(conn, addr) +} + +// Send a message to the proxy client that access was rejected or failed. +func (conn *SocksConn) Reject() error { + return sendSocks4aResponseRejected(conn) +} + +// SocksListener wraps a net.Listener in order to read a SOCKS request on Accept. +// +// func handleConn(conn *pt.SocksConn) error { +// defer conn.Close() +// +// remote, err := net.Dial("tcp", conn.Req.Target) +// if err != nil { +// conn.Reject() +// return err +// } +// err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr)) +// if err != nil { +// return err +// } +// defer remote.Close() +// +// // do something with conn and remote +// } +// ... +// ln, err := pt.ListenSocks("tcp", "127.0.0.1:0") +// if err != nil { +// panic(err.Error()) +// } +// for { +// conn, err := ln.AcceptSocks() +// if err != nil { +// break +// } +// go handleConn(conn) +// } +type SocksListener struct { + net.Listener +} + +// Open a net.Listener according to network and laddr, and return it as a +// SocksListener. +func ListenSocks(network, laddr string) (*SocksListener, error) { + ln, err := net.Listen(network, laddr) + if err != nil { + return nil, err + } + return NewSocksListener(ln), nil +} + +// Create a new SocksListener wrapping the given net.Listener. +func NewSocksListener(ln net.Listener) *SocksListener { + return &SocksListener{ln} +} + +// Accept is the same as AcceptSocks, except that it returns a generic net.Conn. +// It is present for the sake of satisfying the net.Listener interface. +func (ln *SocksListener) Accept() (net.Conn, error) { + return ln.AcceptSocks() +} + +// Call Accept on the wrapped net.Listener, do SOCKS negotiation, and return a +// SocksConn. After accepting, you must call either conn.Grant or conn.Reject +// (presumably after trying to connect to conn.Req.Target). +func (ln *SocksListener) AcceptSocks() (*SocksConn, error) { + c, err := ln.Listener.Accept() + if err != nil { + return nil, err + } + conn := new(SocksConn) + conn.Conn = c + conn.Req, err = readSocks4aConnect(conn) + if err != nil { + conn.Close() + return nil, err + } + return conn, nil +} + +// Read a SOCKS4a connect request. Returns a SocksRequest. +func readSocks4aConnect(s io.Reader) (req SocksRequest, err error) { + r := bufio.NewReader(s) + + var h [8]byte + _, err = io.ReadFull(r, h[:]) + if err != nil { + return + } + if h[0] != socksVersion { + err = errors.New(fmt.Sprintf("SOCKS header had version 0x%02x, not 0x%02x", h[0], socksVersion)) + return + } + if h[1] != socksCmdConnect { + err = errors.New(fmt.Sprintf("SOCKS header had command 0x%02x, not 0x%02x", h[1], socksCmdConnect)) + return + } + + var usernameBytes []byte + usernameBytes, err = r.ReadBytes('\x00') + if err != nil { + return + } + req.Username = string(usernameBytes[:len(usernameBytes)-1]) + + var port int + var host string + + 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 { + return + } + host = string(hostBytes[:len(hostBytes)-1]) + } else { + host = net.IPv4(h[4], h[5], h[6], h[7]).String() + } + + if r.Buffered() != 0 { + err = errors.New(fmt.Sprintf("%d bytes left after SOCKS header", r.Buffered())) + return + } + + req.Target = fmt.Sprintf("%s:%d", host, port) + return +} + +// Send a SOCKS4a response with the given code and address. +func sendSocks4aResponse(w io.Writer, code byte, addr *net.TCPAddr) error { + var resp [8]byte + resp[0] = socksResponseVersion + resp[1] = code + resp[2] = byte((addr.Port >> 8) & 0xff) + resp[3] = byte((addr.Port >> 0) & 0xff) + resp[4] = addr.IP[0] + resp[5] = addr.IP[1] + resp[6] = addr.IP[2] + resp[7] = addr.IP[3] + _, err := w.Write(resp[:]) + return err +} + +var emptyAddr = net.TCPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 0} + +// Send a SOCKS4a response code 0x5a. +func sendSocks4aResponseGranted(w io.Writer, addr *net.TCPAddr) error { + return sendSocks4aResponse(w, socksRequestGranted, addr) +} + +// Send a SOCKS4a response code 0x5b (with an all-zero address). +func sendSocks4aResponseRejected(w io.Writer) error { + return sendSocks4aResponse(w, socksRequestRejected, &emptyAddr) +} diff --git a/socks/socks.go b/socks/socks.go deleted file mode 100644 index 450c409..0000000 --- a/socks/socks.go +++ /dev/null @@ -1,183 +0,0 @@ -// Package socks implements a SOCKS4a server sufficient for a Tor client -// transport plugin. -// -// ln, err := socks.Listen("tcp", ":3128") -// if err != nil { -// return err -// } -// conn, err := ln.AcceptSocks() -// if err != nil { -// return err -// } -// defer conn.Close() -// remote, err := net.Dial("tcp", local.Req.Target) -// if err != nil { -// local.Reject() -// return err -// } -// err = local.Grant(remote.RemoteAddr().(*net.TCPAddr)) -// if err != nil { -// return err -// } -// -// http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol -package socks - -import ( - "bufio" - "errors" - "fmt" - "io" - "net" -) - -const ( - socksVersion = 0x04 - socksCmdConnect = 0x01 - socksResponseVersion = 0x00 - socksRequestGranted = 0x5a - socksRequestRejected = 0x5b -) - -// Request describes a SOCKS request. -type Request struct { - Username string - Target string -} - -// Conn encapsulates a net.Conn and information associated with a SOCKS request. -type Conn struct { - net.Conn - Req Request -} - -// Send a message to the proxy client that access to the given address is -// granted. -func (conn *Conn) Grant(addr *net.TCPAddr) error { - return sendSocks4aResponseGranted(conn, addr) -} - -// Send a message to the proxy client that access was rejected or failed. -func (conn *Conn) Reject() error { - return sendSocks4aResponseRejected(conn) -} - -// Listener wraps a net.Listener in order to read a SOCKS request on Accept. -type Listener struct { - net.Listener -} - -// Open a net.Listener according to network and laddr, and return it as a -// Listener. -func Listen(network, laddr string) (*Listener, error) { - ln, err := net.Listen(network, laddr) - if err != nil { - return nil, err - } - return NewListener(ln), nil -} - -// Create a new Listener wrapping the given net.Listener. -func NewListener(ln net.Listener) *Listener { - return &Listener{ln} -} - -// Accept is the same as AcceptSocks, except that it returns a generic net.Conn. -// It is present for the sake of satisfying the net.Listener interface. -func (ln *Listener) Accept() (net.Conn, error) { - return ln.AcceptSocks() -} - -// Call Accept on the wrapped net.Listener, do SOCKS negotiation, and return a -// Conn. After accepting, you must call either conn.Grant or conn.Reject -// (presumably after trying to connect to conn.Req.Target). -func (ln *Listener) AcceptSocks() (*Conn, error) { - c, err := ln.Listener.Accept() - if err != nil { - return nil, err - } - conn := new(Conn) - conn.Conn = c - conn.Req, err = readSocks4aConnect(conn) - if err != nil { - conn.Close() - return nil, err - } - return conn, nil -} - -// Read a SOCKS4a connect request. Returns a Request. -func readSocks4aConnect(s io.Reader) (req Request, err error) { - r := bufio.NewReader(s) - - var h [8]byte - _, err = io.ReadFull(r, h[:]) - if err != nil { - return - } - if h[0] != socksVersion { - err = errors.New(fmt.Sprintf("SOCKS header had version 0x%02x, not 0x%02x", h[0], socksVersion)) - return - } - if h[1] != socksCmdConnect { - err = errors.New(fmt.Sprintf("SOCKS header had command 0x%02x, not 0x%02x", h[1], socksCmdConnect)) - return - } - - var usernameBytes []byte - usernameBytes, err = r.ReadBytes('\x00') - if err != nil { - return - } - req.Username = string(usernameBytes[:len(usernameBytes)-1]) - - var port int - var host string - - 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 { - return - } - host = string(hostBytes[:len(hostBytes)-1]) - } else { - host = net.IPv4(h[4], h[5], h[6], h[7]).String() - } - - if r.Buffered() != 0 { - err = errors.New(fmt.Sprintf("%d bytes left after SOCKS header", r.Buffered())) - return - } - - req.Target = fmt.Sprintf("%s:%d", host, port) - return -} - -// Send a SOCKS4a response with the given code and address. -func sendSocks4aResponse(w io.Writer, code byte, addr *net.TCPAddr) error { - var resp [8]byte - resp[0] = socksResponseVersion - resp[1] = code - resp[2] = byte((addr.Port >> 8) & 0xff) - resp[3] = byte((addr.Port >> 0) & 0xff) - resp[4] = addr.IP[0] - resp[5] = addr.IP[1] - resp[6] = addr.IP[2] - resp[7] = addr.IP[3] - _, err := w.Write(resp[:]) - return err -} - -var emptyAddr = net.TCPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 0} - -// Send a SOCKS4a response code 0x5a. -func sendSocks4aResponseGranted(w io.Writer, addr *net.TCPAddr) error { - return sendSocks4aResponse(w, socksRequestGranted, addr) -} - -// Send a SOCKS4a response code 0x5b (with an all-zero address). -func sendSocks4aResponseRejected(w io.Writer) error { - return sendSocks4aResponse(w, socksRequestRejected, &emptyAddr) -} diff --git a/socks/socks_test.go b/socks/socks_test.go deleted file mode 100644 index 759bf4a..0000000 --- a/socks/socks_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package socks - -import ( - "bytes" - "net" - "testing" -) - -func tcpAddrsEqual(a, b *net.TCPAddr) bool { - return a.IP.Equal(b.IP) && a.Port == b.Port -} - -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\x04userid"), - // missing hostname - []byte("\x04\x01\x12\x34\x00\x00\x00\x01userid\x00"), - // missing \x00 after hostname - []byte("\x04\x01\x12\x34\x00\x00\x00\x01userid\x00hostname"), - // BIND request - []byte("\x04\x02\x12\x34\x01\x02\x03\x04userid\x00"), - // SOCKS5 - []byte("\x05\x01\x00"), - } - ipTests := [...]struct { - input []byte - userid string - addr net.TCPAddr - }{ - { - []byte("\x04\x01\x12\x34\x01\x02\x03\x04userid\x00"), - "userid", net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 0x1234}, - }, - { - []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 - userid string - target string - }{ - { - []byte("\x04\x01\x12\x34\x00\x00\x00\x01userid\x00hostname\x00"), - "userid", "hostname:4660", - }, - { - []byte("\x04\x01\x12\x34\x00\x00\x00\x01\x00hostname\x00"), - "", "hostname:4660", - }, - { - []byte("\x04\x01\x12\x34\x00\x00\x00\x01userid\x00\x00"), - "userid", ":4660", - }, - { - []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) - } - if req.Username != test.userid { - t.Errorf("%q → username %q (expected %q)", test.input, - req.Username, test.userid) - } - addr, err := net.ResolveTCPAddr("tcp", req.Target) - if err != nil { - t.Error("%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()) - } - } - - 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.Username != test.userid { - t.Errorf("%q → username %q (expected %q)", test.input, - req.Username, test.userid) - } - if req.Target != test.target { - t.Errorf("%q → target %q (expected %q)", test.input, - req.Target, test.target) - } - } -} diff --git a/socks_test.go b/socks_test.go new file mode 100644 index 0000000..bb638b5 --- /dev/null +++ b/socks_test.go @@ -0,0 +1,109 @@ +package pt + +import ( + "bytes" + "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\x04userid"), + // missing hostname + []byte("\x04\x01\x12\x34\x00\x00\x00\x01userid\x00"), + // missing \x00 after hostname + []byte("\x04\x01\x12\x34\x00\x00\x00\x01userid\x00hostname"), + // BIND request + []byte("\x04\x02\x12\x34\x01\x02\x03\x04userid\x00"), + // SOCKS5 + []byte("\x05\x01\x00"), + } + ipTests := [...]struct { + input []byte + userid string + addr net.TCPAddr + }{ + { + []byte("\x04\x01\x12\x34\x01\x02\x03\x04userid\x00"), + "userid", net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 0x1234}, + }, + { + []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 + userid string + target string + }{ + { + []byte("\x04\x01\x12\x34\x00\x00\x00\x01userid\x00hostname\x00"), + "userid", "hostname:4660", + }, + { + []byte("\x04\x01\x12\x34\x00\x00\x00\x01\x00hostname\x00"), + "", "hostname:4660", + }, + { + []byte("\x04\x01\x12\x34\x00\x00\x00\x01userid\x00\x00"), + "userid", ":4660", + }, + { + []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) + } + if req.Username != test.userid { + t.Errorf("%q → username %q (expected %q)", test.input, + req.Username, test.userid) + } + addr, err := net.ResolveTCPAddr("tcp", req.Target) + if err != nil { + t.Error("%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()) + } + } + + 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.Username != test.userid { + t.Errorf("%q → username %q (expected %q)", test.input, + req.Username, test.userid) + } + if req.Target != test.target { + t.Errorf("%q → target %q (expected %q)", test.input, + req.Target, test.target) + } + } +}
tor-commits@lists.torproject.org