goptlib: git.torproject.org/pluggable-transports/goptlib.git Index | Files

package pt

import "git.torproject.org/pluggable-transports/goptlib.git"

Package pt implements the Tor pluggable transports specification.

Sample client usage:

var ptInfo pt.ClientInfo
...
func handler(conn *pt.SocksConn) error {
	defer conn.Close()
	remote, err := net.Dial("tcp", conn.Req.Target)
	if err != nil {
		conn.Reject()
		return err
	}
	defer remote.Close()
	err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr))
	if err != nil {
		return err
	}
	// do something with conn and remote.
	return nil
}
func acceptLoop(ln *pt.SocksListener) error {
	defer ln.Close()
	for {
		conn, err := ln.AcceptSocks()
		if err != nil {
			if e, ok := err.(net.Error); ok && e.Temporary() {
				continue
			}
			return err
		}
		go handler(conn)
	}
	return nil
}
...
func main() {
	var err error
	ptInfo, err = pt.ClientSetup(nil)
	if err != nil {
		os.Exit(1)
	}
	if ptInfo.ProxyURL != nil {
		// you need to interpret the proxy URL yourself
		// call pt.ProxyDone instead if it's a type you understand
		pt.ProxyError(fmt.Sprintf("proxy %s is not supported", ptInfo.ProxyURL))
		os.Exit(1)
	}
	for _, methodName := range ptInfo.MethodNames {
		switch methodName {
		case "foo":
			ln, err := pt.ListenSocks("tcp", "127.0.0.1:0")
			if err != nil {
				pt.CmethodError(methodName, err.Error())
				break
			}
			go acceptLoop(ln)
			pt.Cmethod(methodName, ln.Version(), ln.Addr())
		default:
			pt.CmethodError(methodName, "no such method")
		}
	}
	pt.CmethodsDone()
}

Sample server usage:

var ptInfo pt.ServerInfo
...
func handler(conn net.Conn) error {
	defer conn.Close()
	or, err := pt.DialOr(&ptInfo, conn.RemoteAddr().String(), "foo")
	if err != nil {
		return
	}
	defer or.Close()
	// do something with or and conn
	return nil
}
func acceptLoop(ln net.Listener) error {
	defer ln.Close()
	for {
		conn, err := ln.Accept()
		if err != nil {
			if e, ok := err.(net.Error); ok && e.Temporary() {
				continue
			}
			return err
		}
		go handler(conn)
	}
	return nil
}
...
func main() {
	var err error
	ptInfo, err = pt.ServerSetup(nil)
	if err != nil {
		os.Exit(1)
	}
	for _, bindaddr := range ptInfo.Bindaddrs {
		switch bindaddr.MethodName {
		case "foo":
			ln, err := net.ListenTCP("tcp", bindaddr.Addr)
			if err != nil {
				pt.SmethodError(bindaddr.MethodName, err.Error())
				break
			}
			go acceptLoop(ln)
			pt.Smethod(bindaddr.MethodName, ln.Addr())
		default:
			pt.SmethodError(bindaddr.MethodName, "no such method")
		}
	}
	pt.SmethodsDone()
}

Some additional care is needed to handle signals and shutdown properly. See the example programs dummy-client and dummy-server.

Tor pluggable transports specification: https://spec.torproject.org/pt-spec

Extended ORPort: https://gitweb.torproject.org/torspec.git/tree/proposals/196-transport-control-ports.txt

Extended ORPort Authentication: https://gitweb.torproject.org/torspec.git/tree/proposals/217-ext-orport-auth.txt

Pluggable Transport through SOCKS proxy: https://gitweb.torproject.org/torspec.git/tree/proposals/232-pluggable-transports-through-proxy.txt

The package implements a SOCKS5 server sufficient for a Tor client transport plugin.

https://www.ietf.org/rfc/rfc1928.txt https://www.ietf.org/rfc/rfc1929.txt

Index

Package Files

args.go pt.go socks.go

Constants

const (

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

Variables

var Stdout io.Writer = syncWriter{os.Stdout}

Writer to which pluggable transports negotiation messages are written. It defaults to a Writer that writes to os.Stdout and calls Sync after each write.

You may, for example, log pluggable transports messages by defining a Writer that logs what is written to it:

type logWriteWrapper struct {
	io.Writer
}

func (w logWriteWrapper) Write(p []byte) (int, error) {
	log.Print(string(p))
	return w.Writer.Write(p)
}

and then redefining Stdout:

pt.Stdout = logWriteWrapper{pt.Stdout}

func Cmethod Uses

func Cmethod(name string, socks string, addr net.Addr)

Emit a CMETHOD line. socks must be "socks4" or "socks5". Call this once for each listening client SOCKS port.

func CmethodError Uses

func CmethodError(methodName, msg string) error

Emit a CMETHOD-ERROR line with explanation text. Returns a representation of the error.

func CmethodsDone Uses

func CmethodsDone()

Emit a CMETHODS DONE line. Call this after opening all client listeners.

func DialOr Uses

func DialOr(info *ServerInfo, addr, methodName string) (*net.TCPConn, error)

Dial info.ExtendedOrAddr if defined, or else info.OrAddr, and return an open *net.TCPConn. If connecting to the extended OR port, extended OR port authentication à la 217-ext-orport-auth.txt is done before returning; an error is returned if authentication fails.

The addr and methodName arguments are put in USERADDR and TRANSPORT ExtOrPort commands, respectively. If either is "", the corresponding command is not sent.

func MakeStateDir Uses

func MakeStateDir() (string, error)

Return the directory name in the TOR_PT_STATE_LOCATION environment variable, creating it if it doesn't exist. Returns non-nil error if TOR_PT_STATE_LOCATION is not set or if there is an error creating the directory.

func ProxyDone Uses

func ProxyDone()

Emit a PROXY DONE line. Call this after parsing ClientInfo.ProxyURL.

func ProxyError Uses

func ProxyError(msg string) error

Emit a PROXY-ERROR line with explanation text. Returns a representation of the error.

func Smethod Uses

func Smethod(name string, addr net.Addr)

Emit an SMETHOD line. Call this once for each listening server port.

func SmethodArgs Uses

func SmethodArgs(name string, addr net.Addr, args Args)

Emit an SMETHOD line with an ARGS option. args is a name–value mapping that will be added to the server's extrainfo document.

This is an example of how to check for a required option:

secret, ok := bindaddr.Options.Get("shared-secret")
if ok {
	args := pt.Args{}
	args.Add("shared-secret", secret)
	pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), args)
} else {
	pt.SmethodError(bindaddr.MethodName, "need a shared-secret option")
}

Or, if you just want to echo back the options provided by Tor from the TransportServerOptions configuration,

pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), bindaddr.Options)

func SmethodError Uses

func SmethodError(methodName, msg string) error

Emit an SMETHOD-ERROR line with explanation text. Returns a representation of the error.

func SmethodsDone Uses

func SmethodsDone()

Emit an SMETHODS DONE line. Call this after opening all server listeners.

type Args Uses

type Args map[string][]string

Args maps a string key to a list of values. It is similar to url.Values.

func (Args) Add Uses

func (args Args) Add(key, value string)

Append value to the list of values for key.

func (Args) Get Uses

func (args Args) Get(key string) (value string, ok bool)

Get the first value associated with the given key. If there are any values associated with the key, the value return has the value and ok is set to true. If there are no values for the given key, value is "" and ok is false. If you need access to multiple values, use the map directly.

type Bindaddr Uses

type Bindaddr struct {
    MethodName string
    Addr       *net.TCPAddr
    // Options from TOR_PT_SERVER_TRANSPORT_OPTIONS that pertain to this
    // transport.
    Options Args
}

A combination of a method name and an address, as extracted from TOR_PT_SERVER_BINDADDR.

type ClientInfo Uses

type ClientInfo struct {
    MethodNames []string
    ProxyURL    *url.URL
}

This structure is returned by ClientSetup. It consists of a list of method names and the upstream proxy URL, if any.

func ClientSetup Uses

func ClientSetup(_ []string) (info ClientInfo, err error)

Check the client pluggable transports environment, emitting an error message and returning a non-nil error if any error is encountered. Returns a ClientInfo struct.

If your program needs to know whether to call ClientSetup or ServerSetup (i.e., if the same program can be run as either a client or a server), check whether the TOR_PT_CLIENT_TRANSPORTS environment variable is set:

if os.Getenv("TOR_PT_CLIENT_TRANSPORTS") != "" {
	// Client mode; call pt.ClientSetup.
} else {
	// Server mode; call pt.ServerSetup.
}

Always pass nil for the unused single parameter. In the past, the parameter was a list of transport names to use in case Tor requested "*". That feature was never implemented and has been removed from the pluggable transports specification. https://bugs.torproject.org/15612

type ServerInfo Uses

type ServerInfo struct {
    Bindaddrs      []Bindaddr
    OrAddr         *net.TCPAddr
    ExtendedOrAddr *net.TCPAddr
    AuthCookiePath string
}

This structure is returned by ServerSetup. It consists of a list of Bindaddrs, an address for the ORPort, an address for the extended ORPort (if any), and an authentication cookie (if any).

func ServerSetup Uses

func ServerSetup(_ []string) (info ServerInfo, err error)

Check the server pluggable transports environment, emitting an error message and returning a non-nil error if any error is encountered. Resolves the various requested bind addresses, the server ORPort and extended ORPort, and reads the auth cookie file. Returns a ServerInfo struct.

If your program needs to know whether to call ClientSetup or ServerSetup (i.e., if the same program can be run as either a client or a server), check whether the TOR_PT_CLIENT_TRANSPORTS environment variable is set:

if os.Getenv("TOR_PT_CLIENT_TRANSPORTS") != "" {
	// Client mode; call pt.ClientSetup.
} else {
	// Server mode; call pt.ServerSetup.
}

Always pass nil for the unused single parameter. In the past, the parameter was a list of transport names to use in case Tor requested "*". That feature was never implemented and has been removed from the pluggable transports specification. https://bugs.torproject.org/15612

type SocksConn Uses

type SocksConn struct {
    net.Conn
    Req SocksRequest
}

SocksConn encapsulates a net.Conn and information associated with a SOCKS request.

func (*SocksConn) Grant Uses

func (conn *SocksConn) Grant(addr *net.TCPAddr) error

Send a message to the proxy client that access to the given address is granted. Addr is ignored, and "0.0.0.0:0" is always sent back for BND.ADDR/BND.PORT in the SOCKS response.

func (*SocksConn) Reject Uses

func (conn *SocksConn) Reject() error

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 (*SocksConn) RejectReason Uses

func (conn *SocksConn) RejectReason(reason byte) error

Send a message to the proxy client that access was rejected, with the specific error code indicating the reason behind the rejection.

type SocksListener Uses

type SocksListener struct {
    net.Listener
}

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
	}
	defer remote.Close()
	err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr))
	if err != nil {
		return err
	}
	// do something with conn and remote
	return nil
}
...
ln, err := pt.ListenSocks("tcp", "127.0.0.1:0")
if err != nil {
	panic(err.Error())
}
for {
	conn, err := ln.AcceptSocks()
	if err != nil {
		log.Printf("accept error: %s", err)
		if e, ok := err.(net.Error); ok && e.Temporary() {
			continue
		}
		break
	}
	go handleConn(conn)
}

func ListenSocks Uses

func ListenSocks(network, laddr string) (*SocksListener, error)

Open a net.Listener according to network and laddr, and return it as a SocksListener.

func NewSocksListener Uses

func NewSocksListener(ln net.Listener) *SocksListener

Create a new SocksListener wrapping the given net.Listener.

func (*SocksListener) Accept Uses

func (ln *SocksListener) Accept() (net.Conn, error)

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 (*SocksListener) AcceptSocks Uses

func (ln *SocksListener) AcceptSocks() (*SocksConn, error)

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

Errors returned by AcceptSocks may be temporary (for example, EOF while reading the request, or a badly formatted userid string), or permanent (e.g., the underlying socket is closed). You can determine whether an error is temporary and take appropriate action with a type conversion to net.Error. For example:

for {
	conn, err := ln.AcceptSocks()
	if err != nil {
		if e, ok := err.(net.Error); ok && e.Temporary() {
			log.Printf("temporary accept error; trying again: %s", err)
			continue
		}
		log.Printf("permanent accept error; giving up: %s", err)
		break
	}
	go handleConn(conn)
}

func (*SocksListener) Version Uses

func (ln *SocksListener) Version() string

Returns "socks5", suitable to be included in a call to Cmethod.

type SocksRequest Uses

type SocksRequest struct {
    // The endpoint requested by the client as a "host:port" string.
    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
}

SocksRequest describes a SOCKS request.

Package pt imports 16 packages (graph) and is imported by 24 packages. Updated 2017-06-28. Refresh now. Tools for package owners.