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 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 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
- Variables
- func Cmethod(name string, socks string, addr net.Addr)
- func CmethodError(methodName, msg string) error
- func CmethodsDone()
- func DialOr(info *ServerInfo, addr, methodName string) (*net.TCPConn, error)
- func Log(severity logSeverity, message string)
- func MakeStateDir() (string, error)
- func ProxyDone()
- func ProxyError(msg string) error
- func Smethod(name string, addr net.Addr)
- func SmethodArgs(name string, addr net.Addr, args Args)
- func SmethodError(methodName, msg string) error
- func SmethodsDone()
- type Args
- type Bindaddr
- type ClientInfo
- type ServerInfo
- type SocksConn
- type SocksListener
- type SocksRequest
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 ( LogSeverityError = logSeverity{"error"} LogSeverityWarning = logSeverity{"warning"} LogSeverityNotice = logSeverity{"notice"} LogSeverityInfo = logSeverity{"info"} LogSeverityDebug = logSeverity{"debug"} )
Severity levels for the Log function.
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 ¶
Emit a CMETHOD line. socks must be "socks4" or "socks5". Call this once for each listening client SOCKS port.
func CmethodError ¶
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 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 ¶
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 ¶
Emit a PROXY-ERROR line with explanation text. Returns a representation of the error.
func SmethodArgs ¶
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 ¶
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 ¶
Args maps a string key to a list of values. It is similar to url.Values.
func (Args) Get ¶
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 ¶
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 ¶
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 ¶
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 ¶
Send a message to the proxy client that access was rejected, with the specific error code indicating the reason behind the rejection.
type SocksListener ¶
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. |