123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- /*
- * Copyright (c) 2015, Yawning Angel <yawning at schwanenlied dot me>
- * 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 implements a SOCKS 5 server and the required pluggable
- // transport specific extensions. For more information see RFC 1928 and RFC
- // 1929.
- //
- // Notes:
- // - GSSAPI authentication, is NOT supported.
- // - Only the CONNECT command is supported.
- // - The authentication provided by the client is always accepted as it is
- // used as a channel to pass information rather than for authentication for
- // pluggable transports.
- package socks5 // import "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/lyrebird/common/socks5"
- import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "net"
- "syscall"
- "time"
- "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib"
- )
- const (
- version = 0x05
- rsv = 0x00
- cmdConnect = 0x01
- atypIPv4 = 0x01
- atypDomainName = 0x03
- atypIPv6 = 0x04
- authNoneRequired = 0x00
- authUsernamePassword = 0x02
- authNoAcceptableMethods = 0xff
- requestTimeout = 5 * time.Second
- )
- // ReplyCode is a SOCKS 5 reply code.
- type ReplyCode byte
- // The various SOCKS 5 reply codes from RFC 1928.
- const (
- ReplySucceeded ReplyCode = iota
- ReplyGeneralFailure
- ReplyConnectionNotAllowed
- ReplyNetworkUnreachable
- ReplyHostUnreachable
- ReplyConnectionRefused
- ReplyTTLExpired
- ReplyCommandNotSupported
- ReplyAddressNotSupported
- )
- // Version returns a string suitable to be included in a call to Cmethod.
- func Version() string {
- return "socks5"
- }
- // ErrorToReplyCode converts an error to the "best" reply code.
- func ErrorToReplyCode(err error) ReplyCode {
- opErr, ok := err.(*net.OpError)
- if !ok {
- return ReplyGeneralFailure
- }
- errno, ok := opErr.Err.(syscall.Errno)
- if !ok {
- return ReplyGeneralFailure
- }
- switch errno {
- case syscall.EADDRNOTAVAIL:
- return ReplyAddressNotSupported
- case syscall.ETIMEDOUT:
- return ReplyTTLExpired
- case syscall.ENETUNREACH:
- return ReplyNetworkUnreachable
- case syscall.EHOSTUNREACH:
- return ReplyHostUnreachable
- case syscall.ECONNREFUSED, syscall.ECONNRESET:
- return ReplyConnectionRefused
- default:
- return ReplyGeneralFailure
- }
- }
- // Request describes a SOCKS 5 request.
- type Request struct {
- Target string
- Args pt.Args
- rw *bufio.ReadWriter
- }
- // Handshake attempts to handle a incoming client handshake over the provided
- // connection and receive the SOCKS5 request. The routine handles sending
- // appropriate errors if applicable, but will not close the connection.
- func Handshake(conn net.Conn) (*Request, error) {
- // Arm the handshake timeout.
- var err error
- if err = conn.SetDeadline(time.Now().Add(requestTimeout)); err != nil {
- return nil, err
- }
- defer func() {
- // Disarm the handshake timeout, only propagate the error if
- // the handshake was successful.
- nerr := conn.SetDeadline(time.Time{})
- if err == nil {
- err = nerr
- }
- }()
- req := new(Request)
- req.rw = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
- // Negotiate the protocol version and authentication method.
- var method byte
- if method, err = req.negotiateAuth(); err != nil {
- return nil, err
- }
- // Authenticate if neccecary.
- if err = req.authenticate(method); err != nil {
- return nil, err
- }
- // Read the client command.
- if err = req.readCommand(); err != nil {
- return nil, err
- }
- return req, err
- }
- // Reply sends a SOCKS5 reply to the corresponding request. The BND.ADDR and
- // BND.PORT fields are always set to an address/port corresponding to
- // "0.0.0.0:0".
- func (req *Request) Reply(code ReplyCode) error {
- // The server sends a reply message.
- // uint8_t ver (0x05)
- // uint8_t rep
- // uint8_t rsv (0x00)
- // uint8_t atyp
- // uint8_t bnd_addr[]
- // uint16_t bnd_port
- var resp [4 + 4 + 2]byte
- resp[0] = version
- resp[1] = byte(code)
- resp[2] = rsv
- resp[3] = atypIPv4
- if _, err := req.rw.Write(resp[:]); err != nil {
- return err
- }
- return req.flushBuffers()
- }
- func (req *Request) negotiateAuth() (byte, error) {
- // The client sends a version identifier/selection message.
- // uint8_t ver (0x05)
- // uint8_t nmethods (>= 1).
- // uint8_t methods[nmethods]
- var err error
- if err = req.readByteVerify("version", version); err != nil {
- return 0, err
- }
- // Read the number of methods, and the methods.
- var nmethods byte
- method := byte(authNoAcceptableMethods)
- if nmethods, err = req.readByte(); err != nil {
- return method, err
- }
- methods := make([]byte, int(nmethods))
- if err = req.readFull(methods); err != nil {
- return 0, err
- }
- // Pick the best authentication method, prioritizing authenticating
- // over not if both options are present.
- if bytes.IndexByte(methods, authUsernamePassword) != -1 {
- method = authUsernamePassword
- } else if bytes.IndexByte(methods, authNoneRequired) != -1 {
- method = authNoneRequired
- }
- // The server sends a method selection message.
- // uint8_t ver (0x05)
- // uint8_t method
- msg := []byte{version, method}
- if _, err = req.rw.Write(msg); err != nil {
- return 0, err
- }
- return method, req.flushBuffers()
- }
- func (req *Request) authenticate(method byte) error {
- switch method {
- case authNoneRequired:
- // No authentication required.
- case authUsernamePassword:
- if err := req.authRFC1929(); err != nil {
- return err
- }
- case authNoAcceptableMethods:
- return fmt.Errorf("no acceptable authentication methods")
- default:
- // This should never happen as only supported auth methods should be
- // negotiated.
- return fmt.Errorf("negotiated unsupported method 0x%02x", method)
- }
- return req.flushBuffers()
- }
- func (req *Request) readCommand() error {
- // The client sends the request details.
- // uint8_t ver (0x05)
- // uint8_t cmd
- // uint8_t rsv (0x00)
- // uint8_t atyp
- // uint8_t dst_addr[]
- // uint16_t dst_port
- var err error
- if err = req.readByteVerify("version", version); err != nil {
- _ = req.Reply(ReplyGeneralFailure)
- return err
- }
- if err = req.readByteVerify("command", cmdConnect); err != nil {
- _ = req.Reply(ReplyCommandNotSupported)
- return err
- }
- if err = req.readByteVerify("reserved", rsv); err != nil {
- _ = req.Reply(ReplyGeneralFailure)
- return err
- }
- // Read the destination address/port.
- var atyp byte
- var host string
- if atyp, err = req.readByte(); err != nil {
- _ = req.Reply(ReplyGeneralFailure)
- return err
- }
- switch atyp {
- case atypIPv4:
- var addr [net.IPv4len]byte
- if err = req.readFull(addr[:]); err != nil {
- _ = req.Reply(ReplyGeneralFailure)
- return err
- }
- host = net.IPv4(addr[0], addr[1], addr[2], addr[3]).String()
- case atypDomainName:
- var alen byte
- if alen, err = req.readByte(); err != nil {
- _ = req.Reply(ReplyGeneralFailure)
- return err
- }
- if alen == 0 {
- _ = req.Reply(ReplyGeneralFailure)
- return fmt.Errorf("domain name with 0 length")
- }
- addr := make([]byte, int(alen))
- if err = req.readFull(addr); err != nil {
- _ = req.Reply(ReplyGeneralFailure)
- return err
- }
- host = string(addr)
- case atypIPv6:
- var addr [net.IPv6len]byte
- if err = req.readFull(addr[:]); err != nil {
- _ = req.Reply(ReplyGeneralFailure)
- return err
- }
- host = fmt.Sprintf("[%s]", net.IP(addr[:]).String())
- default:
- _ = req.Reply(ReplyAddressNotSupported)
- return fmt.Errorf("unsupported address type 0x%02x", atyp)
- }
- var rawPort [2]byte
- if err = req.readFull(rawPort[:]); err != nil {
- _ = req.Reply(ReplyGeneralFailure)
- return err
- }
- port := int(rawPort[0])<<8 | int(rawPort[1])
- req.Target = fmt.Sprintf("%s:%d", host, port)
- return req.flushBuffers()
- }
- func (req *Request) flushBuffers() error {
- if err := req.rw.Flush(); err != nil {
- return err
- }
- if req.rw.Reader.Buffered() > 0 {
- return fmt.Errorf("read buffer has %d bytes of trailing data", req.rw.Reader.Buffered())
- }
- return nil
- }
- func (req *Request) readByte() (byte, error) {
- return req.rw.ReadByte()
- }
- func (req *Request) readByteVerify(descr string, expected byte) error {
- val, err := req.rw.ReadByte()
- if err != nil {
- return err
- }
- if val != expected {
- return fmt.Errorf("message field '%s' was 0x%02x (expected 0x%02x)", descr, val, expected)
- }
- return nil
- }
- func (req *Request) readFull(buf []byte) error {
- _, err := io.ReadFull(req.rw, buf)
- return err
- }
|