pt

package module
v3.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 7, 2022 License: MIT Imports: 18 Imported by: 2

Documentation

Overview

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("proxy %s is not supported")
		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

Constants

View Source
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

View Source
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}

Functions

func Cmethod

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

func CmethodError(methodName, msg string) error

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

func CmethodsDone

func CmethodsDone()

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

func DialOr

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 Getenv

func Getenv(key string) string

func GetenvRequired

func GetenvRequired(key string) (string, error)

Returns an ENV-ERROR if the environment variable isn't set.

func MakeStateDir

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 ParsePT2ClientParameters

func ParsePT2ClientParameters(s string) (map[string]interface{}, error)

func ParsePT2ServerParameters

func ParsePT2ServerParameters(s string) (params map[string]map[string]interface{}, err error)

func ProxyDone

func ProxyDone()

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

func ProxyError

func ProxyError(msg string) error

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

func ResolveAddr

func ResolveAddr(addrStr string) (*net.TCPAddr, error)

Resolve an address string into a net.TCPAddr. We are a bit more strict than net.ResolveTCPAddr; we don't allow an empty host or port, and the host part must be a literal IP address.

func Smethod

func Smethod(name string, addr net.Addr)

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

func SmethodArgs

func SmethodArgs(name string, addr net.Addr, args map[string]interface{})

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

func SmethodError(methodName, msg string) error

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

func SmethodsDone

func SmethodsDone()

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

Types

type Bindaddr

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

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

func FilterBindaddrs

func FilterBindaddrs(addrs []Bindaddr, methodNames []string) []Bindaddr

Return a new slice, the members of which are those members of addrs having a MethodName in methodNames.

type ClientInfo

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

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://trac.torproject.org/projects/tor/ticket/15612

type ServerInfo

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

type SocksConn

type SocksConn struct {
	net.Conn
	Req SocksRequest
}

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

func (*SocksConn) Grant

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

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

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

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

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

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

func NewSocksListener

func NewSocksListener(ln net.Listener) *SocksListener

Create a new SocksListener wrapping the given net.Listener.

func (*SocksListener) Accept

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

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

func (ln *SocksListener) Version() string

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

type SocksRequest

type SocksRequest struct {
	// The endpoint requested by the client as a "host:port" string.
	Target string
	// The parsed contents of private authentication method as a key–value mapping.
	Args map[string]interface{}
}

SocksRequest describes a SOCKS request.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL