From 73ef2e624c10aea53228286902ea62ce2f8a9686 Mon Sep 17 00:00:00 2001 From: Gaukas Wang Date: Thu, 25 May 2023 08:56:58 -0600 Subject: [PATCH] docs: add comments and docs - modify README.md - add more comments! --- README.md | 22 ++++++++++++++++++---- addr.go | 4 ++++ authentication_methods.go | 3 +++ authenticator.go | 2 ++ logging.go | 10 ++++++++++ packets.go | 14 ++++++++++++++ proxy.go | 3 +-- server.go | 2 ++ server_test.go | 1 - 9 files changed, 54 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6b54061..1e888d6 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # socks5 -A sub-RFC1928 SOCKS5 implementation in pure Go with no external dependency. +A sub-RFC1928 SOCKS5 server supporting custom transport layer implemented in pure Go with no external dependency. ## Overview -This package implements the SOCKS5 protocol as described in [RFC1928](https://tools.ietf.org/html/rfc1928) in Go with no external dependency. It is designed to be used as a library in other projects, for purposes including but not limited to: proxy client/server, traffic analysis, etc. +This package implements the SOCKS5 protocol as described in [RFC1928](https://tools.ietf.org/html/rfc1928) in Go with no external dependency. Unlike a traditional SOCKS5 server, this implementation separates the SOCKS5 server from the **actual** proxy server, which allows it to be used with any custom transport and/or in other applications. -### Features +### SOCKS5 Features - Authentication Methods - [x] NO AUTHENTICATION REQUIRED @@ -19,4 +19,18 @@ This package implements the SOCKS5 protocol as described in [RFC1928](https://to ## Usage -See `example/min` for a minimal example with a minimal SOCKS5 proxy module implementation. \ No newline at end of file +It is mandatory to provide a `Proxy` implementation to spin up a SOCKS5 `Server` with this package. + +A `Proxy` interface provides a general-purpose proxy backend service with following methods: + +```go +type Proxy interface { + Connect(dst net.Addr) (conn net.Conn, err error) + Bind(dst net.Addr) (net.Listener, error) + UDPAssociate() (net.PacketConn, error) +} +``` + +Essentially, by allowing custom `Proxy`, this package enables high programmability and flexibility for how SOCKS5 server proxies network traffic. It is possible to implement a `Proxy` that proxies traffic via another remote server via some more complex protocol such as TLS. + +An example of a `Proxy` implementation can be found as `localProxy` in `proxy.go`. If a `Server` is spun up with this `localProxy`, it will act as a traditional SOCKS5 server that proxies traffic directly from the machine it runs on. \ No newline at end of file diff --git a/addr.go b/addr.go index 9c0f685..96e0277 100644 --- a/addr.go +++ b/addr.go @@ -73,18 +73,22 @@ func newAddr(network, adr string) *addr { } } +// Network interfaces net.Addr func (a *addr) Network() string { return a.network } +// String interfaces net.Addr func (a *addr) String() string { return a.addr } +// StringEqual compares the string representation an addr with another net.Addr func (a *addr) StringEqual(b net.Addr) bool { return a.String() == b.String() } +// HostMatching compares the host part of an addr with another net.Addr func (a *addr) HostMatching(b net.Addr) bool { // split the host and port from hostOrAddr host, _, err := net.SplitHostPort(b.String()) diff --git a/authentication_methods.go b/authentication_methods.go index 7e535af..5c593d4 100644 --- a/authentication_methods.go +++ b/authentication_methods.go @@ -5,6 +5,7 @@ import ( "net" ) +// AuthenticationMethod is an interface that represents a custom SOCKS5 authentication method. type AuthenticationMethod interface { // Authenticate will respond to the net.Conn with the selected authentication method. // Then, the AuthenticationMethod should proceed and finish the authentication process. @@ -25,6 +26,7 @@ func (*NoAuthenticationRequired) Authenticate(conn net.Conn) error { return authSelect.Write(conn) } +// UsernamePassword is a AuthenticationMethod that requires username/password authentication. type UsernamePassword struct { UserPass map[string]string } @@ -34,6 +36,7 @@ const ( USERPASS_AUTH_FAILURE byte = 0x01 ) +// Authenticate implements the AuthenticationMethod interface. func (up *UsernamePassword) Authenticate(conn net.Conn) error { var authSelect *PacketAuthSelect = &PacketAuthSelect{PROTOCOL_VERSION, USERNAME_PASSWORD} err := authSelect.Write(conn) diff --git a/authenticator.go b/authenticator.go index 8672257..36b414e 100644 --- a/authenticator.go +++ b/authenticator.go @@ -5,6 +5,8 @@ import ( "net" ) +// Authenticator is used to authenticate the client. It supports Username/Password method +// (and No Auth method) by default and allows custom authentication methods via PrivateMethods. type Authenticator struct { Forced bool // default: false, if set to true, NO_AUTHENTICATION_REQUIRED is not accepted UserPass map[string]string diff --git a/logging.go b/logging.go index 72613bd..b6a3617 100644 --- a/logging.go +++ b/logging.go @@ -21,33 +21,43 @@ type Logger interface { // noLogger is a no-op logger type noLogger struct{} +// Debug interfaces Logger func (*noLogger) Debug(_ ...any) { } +// Debugf interfaces Logger func (*noLogger) Debugf(_ string, _ ...any) { } +// Info interfaces Logger func (*noLogger) Info(_ ...any) { } +// Infof interfaces Logger func (*noLogger) Infof(_ string, _ ...any) { } +// Warn interfaces Logger func (*noLogger) Warn(_ ...any) { } +// Warnf interfaces Logger func (*noLogger) Warnf(_ string, _ ...any) { } +// Error interfaces Logger func (*noLogger) Error(_ ...any) { } +// Errorf interfaces Logger func (*noLogger) Errorf(_ string, _ ...any) { } +// Fatal interfaces Logger func (*noLogger) Fatal(_ ...any) { } +// Fatalf interfaces Logger func (*noLogger) Fatalf(_ string, _ ...any) { } diff --git a/packets.go b/packets.go index 03e5e33..bb646f0 100644 --- a/packets.go +++ b/packets.go @@ -40,6 +40,7 @@ type PacketAuthRequest struct { METHODS []byte // length: NMETHODS } +// Read interfaces Packet func (p *PacketAuthRequest) Read(r io.Reader) error { // Read VER and NMETHODS hdr := make([]byte, 2) @@ -73,6 +74,7 @@ func (p *PacketAuthRequest) Read(r io.Reader) error { return nil } +// Write interfaces Packet func (*PacketAuthRequest) Write(_ io.Writer) error { return fmt.Errorf("not implemented for client-sent packet") } @@ -93,10 +95,12 @@ type PacketAuthSelect struct { METHOD byte } +// Read interfaces Packet func (*PacketAuthSelect) Read(_ io.Reader) error { return fmt.Errorf("not implemented for server-sent packet") } +// Write interfaces Packet func (p *PacketAuthSelect) Write(w io.Writer) error { n, err := w.Write([]byte{p.VER, p.METHOD}) if err != nil { @@ -131,6 +135,7 @@ const ( USERPASS_AUTH_VERSION byte = 0x01 ) +// Read interfaces Packet func (p *PacketUserPassAuth) Read(r io.Reader) error { // Read VER, ULEN verulen := make([]byte, 2) @@ -192,6 +197,7 @@ func (p *PacketUserPassAuth) Read(r io.Reader) error { return nil } +// Write interfaces Packet func (*PacketUserPassAuth) Write(_ io.Writer) error { return fmt.Errorf("not implemented for client-sent packet") } @@ -212,10 +218,12 @@ type PacketUserPassAuthStatus struct { STATUS byte } +// Read interfaces Packet func (*PacketUserPassAuthStatus) Read(_ io.Reader) error { return fmt.Errorf("not implemented for server-sent packet") } +// Write interfaces Packet func (p *PacketUserPassAuthStatus) Write(w io.Writer) error { n, err := w.Write([]byte{p.VER, p.STATUS}) if err != nil { @@ -257,6 +265,7 @@ const ( REQUEST_ATYP_IPV6 byte = 0x04 ) +// Read interfaces Packet func (p *PacketRequest) Read(r io.Reader) error { // Read VER, CMD, RSV, ATYP vercmdrsvatyp := make([]byte, 4) @@ -351,6 +360,7 @@ func (p *PacketRequest) Read(r io.Reader) error { return nil } +// Write interfaces Packet func (*PacketRequest) Write(_ io.Writer) error { return fmt.Errorf("not implemented for client-sent packet") } @@ -391,10 +401,12 @@ const ( REPLY_ATYP_IPV6 byte = 0x04 ) +// Read interfaces Packet func (*PacketReply) Read(_ io.Reader) error { return fmt.Errorf("not implemented for server-sent packet") } +// Write interfaces Packet func (p *PacketReply) Write(w io.Writer) error { if p.VER == 0x00 { p.VER = PROTOCOL_VERSION // by default, use the implemented version @@ -472,6 +484,7 @@ const ( UDP_RSV_EXPECTED uint16 = 0x0000 ) +// Read reads next UDP request from the given packet connection. func (p *PacketUDPRequest) Read(pc net.PacketConn) error { var buf []byte = make([]byte, 65535) var n int @@ -512,6 +525,7 @@ func (p *PacketUDPRequest) Read(pc net.PacketConn) error { return nil } +// Write writes the UDP request to the given packet connection. func (p *PacketUDPRequest) Write(pc net.PacketConn) error { var bndaddr []byte var err error diff --git a/proxy.go b/proxy.go index 734ebb9..c6d2640 100644 --- a/proxy.go +++ b/proxy.go @@ -6,8 +6,7 @@ import ( "net" ) -// Proxy is the interface for an underlying transport which delivers proxy requests and responses -// between the client and the proxy worker server. +// Proxy is the interface for an underlying implementation of a general-purpose proxy. type Proxy interface { // Connect creates an outgoing (TCP) connection to the destination (dst) from the proxy server. // diff --git a/server.go b/server.go index 2280de3..41d69f0 100644 --- a/server.go +++ b/server.go @@ -21,6 +21,8 @@ type Server struct { wg *sync.WaitGroup } +// NewServer creates a new Server. +// Wrap or Listen must be called explicitly before the server can accept connections. func NewServer(auth *Authenticator, proxy Proxy, logger Logger) (*Server, error) { if proxy == nil { return nil, errors.New("no proxy provided") diff --git a/server_test.go b/server_test.go index 78757c7..6915e2c 100644 --- a/server_test.go +++ b/server_test.go @@ -12,7 +12,6 @@ import ( // - SOCKS5 Proxy Server binds to 127.0.0.2:8080 // - Underlying Proxy Server Implementation works on 127.0.1.2 // - Dummy Remote Destination binds to 127.0.0.3:8080 - func TestServer(t *testing.T) { t.Run("HandleConnIPv4", func(t *testing.T) { t.Run("CmdConnectIPv4", testHandleCmdConnectIPv4)