pt

package module
v1.6.0 Latest Latest
Warning

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

Go to latest
Published: Nov 14, 2024 License: CC0-1.0 Imports: 16 Imported by: 54

README

goptlib is a library for writing Tor pluggable transports in Go.

https://spec.torproject.org/pt-spec
https://spec.torproject.org/ext-orport-spec

To download a copy of the library into $GOPATH:
	go get gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib

See the included example programs for examples of how to use the
library. To build them, enter their directory and run "go build".
	examples/dummy-client/dummy-client.go
	examples/dummy-server/dummy-server.go
The recommended way to start writing a new transport plugin is to copy
dummy-client or dummy-server and make changes to it.

There is browseable documentation here:
https://pkg.go.dev/gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib

Report bugs to the tor-dev@lists.torproject.org mailing list or to the
bug tracker at https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib/-/issues.

To the extent possible under law, the authors have dedicated all
copyright and related and neighboring rights to this software to the
public domain worldwide. This software is distributed without any
warranty. See COPYING.

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() {
				pt.Log(pt.LogSeverityError, "accept error: " + err.Error())
				continue
			}
			return err
		}
		go handler(conn)
	}
	return nil
}
...
func main() {
	var err error
	pt.ReportVersion("program", "v1.0")
	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
			}
			pt.Log(pt.LogSeverityError, "accept error: " + err.Error())
			return err
		}
		go handler(conn)
	}
	return nil
}
...
func main() {
	var err error
	pt.ReportVersion("program", "v1.0")
	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://spec.torproject.org/ext-orport-spec

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 (
	LogSeverityError   = logSeverity{"error"}
	LogSeverityWarning = logSeverity{"warning"}
	LogSeverityNotice  = logSeverity{"notice"}
	LogSeverityInfo    = logSeverity{"info"}
	LogSeverityDebug   = logSeverity{"debug"}
)

Severity levels for the Log function.

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 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 DialOrWithDialer added in v1.4.0

func DialOrWithDialer(dialer *net.Dialer, info *ServerInfo, addr, methodName string) (net.Conn, error)

Dial (using the given net.Dialer) info.ExtendedOrAddr if defined, or else info.OrAddr, and return an open net.Conn. If connecting to the extended OR port, extended OR port authentication 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 Log added in v1.1.0

func Log(severity logSeverity, message string)

Emit a LOG message with the given severity (one of LogSeverityError, LogSeverityWarning, LogSeverityNotice, LogSeverityInfo, or LogSeverityDebug).

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 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 ReportVersion added in v1.5.0

func ReportVersion(implementation string, version string)

Report this program's name and version number using a STATUS TYPE=version line. Can be called any time before calling CmethodsDone or SmethodsDone.

When called in a pluggable transport server, the implementation name and version number will appear in the relay's bridge-extra-info descriptor.

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

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 Args

type Args map[string][]string

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

func (Args) Add

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

Append value to the list of values for key.

func (Args) Get

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

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

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://bugs.torproject.org/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).

func ServerSetup

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

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

Directories

Path Synopsis
examples
dummy-client
Dummy no-op pluggable transport client.
Dummy no-op pluggable transport client.
dummy-server
Dummy no-op pluggable transport server.
Dummy no-op pluggable transport server.

Jump to

Keyboard shortcuts

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