Skip to content

Commit

Permalink
feat: add support for windows and mac
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanbekhen committed Nov 26, 2023
1 parent d49028c commit 3fad371
Show file tree
Hide file tree
Showing 22 changed files with 1,183 additions and 858 deletions.
Empty file removed .env.sample
Empty file.
51 changes: 47 additions & 4 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,31 @@ project_name: nanoproxy
dist: dist
before:
hooks:
- touch .env
- echo '[Unit]
Description=NanoProxy is a simple reverse proxy written in Go
After=network.target
[Service]
EnvironmentFile=/etc/nanoproxy/nanoproxy
ExecStart=/usr/bin/nanoproxy
WorkingDirectory=/usr/bin
Restart=always
User=root
[Install]
WantedBy=multi-user.target' > nanoproxy.service
- go mod tidy
builds:

builds:
- binary: nanoproxy
ldflags:
- -s -w -X github.com/ryanbekhen/nanoproxy.Version={{ .Version }}
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
- windows

dockers:
- image_templates:
- "ghcr.io/ryanbekhen/nanoproxy:{{ .Version }}"
Expand All @@ -35,24 +50,52 @@ nfpms:
license: "MIT"
vendor: ryanbekhen
contents:
- src: systemd/nanoproxy.service
- src: nanoproxy.service
dst: /etc/systemd/system/nanoproxy.service
type: "config|noreplace"
- src: .env.sample
- src: .env
dst: /etc/nanoproxy/nanoproxy
type: "config|noreplace"
formats:
- apk
- deb
- rpm

brews:
- name: nanoproxy
homepage: https://github.com/ryanbekhen/nanoproxy
description: "nanoproxy is a simple reverse proxy written in Go."
license: "MIT"
folder: Formula
install: |
bin.install "nanoproxy"
winget:
- name: nanoproxy
package_identifier: ryanbekhen.nanoproxy
publisher: ryanbekhen
description: "nanoproxy is a simple reverse proxy written in Go."
short_description: "nanoproxy is a simple reverse proxy written in Go."
publisher_url: https://ryanbekhen.dev
publisher_support_url: https://ryanbekhen.dev
license: MIT
license_url: https://github.com/ryanbekhen/nanoproxy/blob/master/LICENSE
homepage: https://github.com/ryanbekhen/nanoproxy
author: ryanbekhen
release_notes: {{.Changelog}}
copyright: ryanbekhen
copyright_url: https://github.com/ryanbekhen/nanoproxy/blob/master/LICENSE
tags:
- Proxy
- Socks5

release:
draft: false

publishers:
- name: fury.io
dir: "{{ dir .ArtifactPath }}"
cmd: curl -F package=@{{ .ArtifactName }} https://{{ .Env.FURY_TOKEN }}@push.fury.io/ryanbekhen/
cmd: 'if [[ {{ .ArtifactName }} == *.deb || {{ .ArtifactName }} == *.rpm ]]; then curl -F package=@{{ .ArtifactName }} https://{{ .Env.FURY_TOKEN }}@push.fury.io/ryanbekhen/; fi'

archives:
- name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
Expand Down
6 changes: 3 additions & 3 deletions .testcoverage.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
profile: cover.out
local-prefix: "github.com/ryanbekhen/nanoproxy"
threshold:
file: 50
package: 60
total: 60
file: 60
package: 80
total: 80
exclude:
paths:
- \.pb\.go$ # excludes all protobuf generated files
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ require (
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
golang.org/x/sys v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA=
github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
Expand All @@ -12,11 +14,15 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand All @@ -25,3 +31,6 @@ golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
15 changes: 10 additions & 5 deletions nanoproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/rs/zerolog"
"github.com/ryanbekhen/nanoproxy/pkg/config"
"github.com/ryanbekhen/nanoproxy/pkg/socks5"
"net"
"os"
"time"

Expand All @@ -24,14 +25,18 @@ func main() {
time.Local = loc
}

socks5Config := &socks5.Config{
socks5Config := socks5.Config{
Logger: &logger,
AfterRequest: func(req *socks5.Request, conn net.Conn) {
logger.Info().
Str("client_addr", conn.RemoteAddr().String()).
Str("dest_addr", req.DestAddr.String()).
Str("latency", req.Latency.String()).
Msg("request processed")
},
}

sock5Server, err := socks5.New(socks5Config)
if err != nil {
logger.Fatal().Msg(err.Error())
}
sock5Server := socks5.New(&socks5Config)

logger.Info().Msgf("Starting socks5 server on %s://%s", cfg.Network, cfg.ADDR)
if err := sock5Server.ListenAndServe(cfg.Network, cfg.ADDR); err != nil {
Expand Down
121 changes: 58 additions & 63 deletions pkg/socks5/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,91 @@ import (
"io"
)

// AuthType is the type of authentication used by the client to connect to the proxy server (see RFC 1928, Section 2)
type AuthType uint8

func (a AuthType) Uint8() uint8 {
return uint8(a)
}

// AuthStatus is the status of the authentication process
type AuthStatus uint8

func (a AuthStatus) Uint8() uint8 {
return uint8(a)
}

const (
NoAuth = uint8(0)
noAcceptable = uint8(255)
UserPassAuth = uint8(2)
userAuthVersion = uint8(1)
authSuccess = uint8(0)
authFailure = uint8(1)
NoAuth AuthType = 0x00
NoAcceptable AuthType = 0xFF
UserPassAuth AuthType = 0x02

AuthSuccess AuthStatus = 0x00
AuthFailure AuthStatus = 0x01

UserAuthVersion = 0x01
)

var (
UserAuthFailed = fmt.Errorf("user authentication failed")
NoSupportedAuth = fmt.Errorf("no supported authentication mechanism")
ErrAuthFailure = fmt.Errorf("authentication failure")
ErrNoSupportedAuth = fmt.Errorf("no supported authentication mechanism")
)

// AuthContext A Request encapsulates authentication state provided
// during negotiation
// AuthContext encapsulates authentication state provided during negotiation
type AuthContext struct {
// Provided auth method
Method uint8
// Method is the provided auth method
Method AuthType
// Payload provided during negotiation.
// Keys depend on the used auth method.
// For UserPass-auth contains Username
Payload map[string]string
}

// Authenticator is the interface implemented by types that can handle authentication
type Authenticator interface {
Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error)
GetCode() uint8
GetCode() AuthType
}

// NoAuthAuthenticator is used to handle the "No Authentication" mode
type NoAuthAuthenticator struct{}

func (a NoAuthAuthenticator) GetCode() uint8 {
// GetCode returns the code of the authenticator
func (a *NoAuthAuthenticator) GetCode() AuthType {
return NoAuth
}

func (a NoAuthAuthenticator) Authenticate(_ io.Reader, writer io.Writer) (*AuthContext, error) {
_, err := writer.Write([]byte{socks5Version, NoAuth})
// Authenticate handles the authentication process
func (a *NoAuthAuthenticator) Authenticate(_ io.Reader, writer io.Writer) (*AuthContext, error) {
_, err := writer.Write([]byte{Version, uint8(NoAuth)})
return &AuthContext{NoAuth, nil}, err
}

// UserPassAuthenticator is used to handle username/password-based
// authentication
// UserPassAuthenticator is used to handle username/password-based authentication
type UserPassAuthenticator struct {
Credentials CredentialStore
}

func (a UserPassAuthenticator) GetCode() uint8 {
// GetCode returns the code of the authenticator
func (a *UserPassAuthenticator) GetCode() AuthType {
return UserPassAuth
}

func (a UserPassAuthenticator) Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error) {
// Tell the client to use user/pass auth
if _, err := writer.Write([]byte{socks5Version, UserPassAuth}); err != nil {
// Authenticate handles the authentication process
func (a *UserPassAuthenticator) Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error) {
if _, err := writer.Write([]byte{Version, uint8(UserPassAuth)}); err != nil {
return nil, err
}

// Get the version and username length
// Read the version byte
header := []byte{0, 0}
if _, err := io.ReadAtLeast(reader, header, 2); err != nil {
return nil, err
}

// Ensure we are compatible
if header[0] != userAuthVersion {
return nil, fmt.Errorf("unsupported auth version: %v", header[0])
if header[0] != UserAuthVersion {
return nil, ErrUnsupportedAuthVersion
}

// Get the username
Expand All @@ -93,61 +111,38 @@ func (a UserPassAuthenticator) Authenticate(reader io.Reader, writer io.Writer)
return nil, err
}

// Verify the password
// Check the credentials
if a.Credentials.Valid(string(user), string(pass)) {
if _, err := writer.Write([]byte{userAuthVersion, authSuccess}); err != nil {
if _, err := writer.Write([]byte{UserAuthVersion, uint8(AuthSuccess)}); err != nil {
return nil, err
}
} else {
if _, err := writer.Write([]byte{userAuthVersion, authFailure}); err != nil {
if _, err := writer.Write([]byte{UserAuthVersion, uint8(AuthFailure)}); err != nil {
return nil, err
}
return nil, UserAuthFailed
return nil, ErrAuthFailure
}

// Done
return &AuthContext{UserPassAuth, map[string]string{"Username": string(user)}}, nil
}

// authenticate is used to handle connection authentication
func (s *Server) authenticate(conn io.Writer, bufConn io.Reader) (*AuthContext, error) {
// Get the methods
methods, err := readMethods(bufConn)
if err != nil {
return nil, fmt.Errorf("failed to get auth methods: %v", err)
func readMethods(bufConn io.Reader) ([]byte, error) {
header := []byte{0}
if _, err := bufConn.Read(header); err != nil {
return nil, err
}

// Select a usable method
for _, method := range methods {
if auth, ok := s.authMethods[method]; ok {
return auth.Authenticate(bufConn, conn)
}
}
numMethods := int(header[0])
methods := make([]byte, numMethods)
_, err := io.ReadAtLeast(bufConn, methods, numMethods)

// No usable method found
return nil, noAcceptableAuth(conn)
return methods, err
}

// noAcceptableAuth is used to handle when we have no eligible
// authentication mechanism
func noAcceptableAuth(conn io.Writer) error {
_, err := conn.Write([]byte{socks5Version, noAcceptable})
func noAcceptable(conn io.Writer) error {
_, err := conn.Write([]byte{Version, uint8(NoAcceptable)})
if err != nil {
return err
}
return NoSupportedAuth
}

// readMethods is used to read the number of methods
// and proceeding auth methods
func readMethods(r io.Reader) ([]byte, error) {
header := []byte{0}
if _, err := r.Read(header); err != nil {
return nil, err
}

numMethods := int(header[0])
methods := make([]byte, numMethods)
_, err := io.ReadAtLeast(r, methods, numMethods)
return methods, err
return ErrNoSupportedAuth
}
Loading

0 comments on commit 3fad371

Please sign in to comment.