Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features/support http proxy #29

Merged
merged 7 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .testcoverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ profile: cover.out
local-prefix: "github.com/ryanbekhen/nanoproxy"
threshold:
file: 60
package: 80
package: 60
total: 80
exclude:
paths:
- \.pb\.go$ # excludes all protobuf generated files
- nanoproxy\.go$ # excludes the main package
- nanoproxy\.go$ # excludes the main package
- pkg/config/.*\.go$ # excludes all files in the pkg/config package
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ FROM busybox:1.36.1-glibc

COPY nanoproxy /usr/bin/nanoproxy
EXPOSE 1080
EXPOSE 8080

ENTRYPOINT ["nanoproxy"]
1 change: 1 addition & 0 deletions Dockerfile-tor
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ RUN mkdir -p /etc/tor && \
RUN mkdir -p /var/lib/tor

EXPOSE 1080
EXPOSE 8080

ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
93 changes: 76 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ Note: This code includes modifications from the original go-socks5 project (http
Modifications have been made as part of maintenance for NanoProxy.
This version is licensed under the MIT license.

NanoProxy is a lightweight SOCKS5 proxy server written in Go. It is designed to be simple, minimalistic, and easy to
use.
NanoProxy is a lightweight proxy server written in Go. It supports both **SOCKS5** and **HTTP Proxy** protocols, making
it flexible for proxying various types of network traffic. NanoProxy is designed to be simple, minimalistic, and easy to
use. It can be run as a standalone service or as a Docker container.

> ⚠️ **Notice:** NanoProxy is currently in pre-production stage. While it provides essential proxying capabilities,
> please be aware that it is still under active development. Full backward compatibility is not guaranteed until
Expand All @@ -17,10 +18,10 @@ use.

## Data Flow Through Proxy

NanoProxy acts as a proxy server that forwards network traffic between the user and the destination server.
When a user makes a request, the request is sent to the proxy server. The proxy server then forwards the request to
the destination server. The destination server processes the request and responds back to the proxy server, which then
sends the response back to the user. This allows the proxy server to intercept and manage network traffic effectively.
NanoProxy acts as a proxy Server that forwards network traffic between the user and the destination Server.
When a user makes a request, the request is sent to the proxy Server. The proxy Server then forwards the request to
the destination Server. The destination Server processes the request and responds back to the proxy Server, which then
sends the response back to the user. This allows the proxy Server to intercept and manage network traffic effectively.

Here's how the data flows through the proxy:

Expand Down Expand Up @@ -60,6 +61,27 @@ sequenceDiagram
NanoProxy ->> User: Respond
```

## Data Flow Through Proxy with HTTP Support

NanoProxy supports HTTP proxying by handling HTTP requests and forwarding them to the destination server. Depending on
the request method (e.g., GET, POST, CONNECT), NanoProxy processes and forwards the request accordingly.

Here's how the data flows through the proxy when using HTTP support:

```mermaid
sequenceDiagram
participant User
participant NanoProxy
participant DestinationServer
User ->> NanoProxy: HTTP Request
NanoProxy ->> DestinationServer: Forward HTTP Request
DestinationServer ->> NanoProxy: Process & Respond
NanoProxy ->> User: Deliver Response
```

This process allows NanoProxy to act as an intermediary between the client and the destination server for HTTP traffic,
ensuring flexibility and traffic management.

### Features of NanoProxy with Tor:

- **Enhanced Anonymity**: Traffic is routed through multiple Tor nodes, making it difficult to trace the origin of the
Expand All @@ -68,7 +90,7 @@ sequenceDiagram
- **Secure Data Transmission**: Encryption between Tor nodes protects data from snooping.

This distinct data flow employing the Tor network ensures that users enjoy increased privacy without compromising on the
flexible functionality of the proxy server.
flexible functionality of the proxy Server.

### Impact of Using NanoProxy with Tor:

Expand All @@ -87,8 +109,9 @@ especially if anonymity is prioritized over connection speed or stability.

NanoProxy provides the following features:

- [x] **SOCKS5 proxy server.** NanoProxy is a SOCKS5 proxy server that can be used to proxy network traffic for various
- [x] **SOCKS5 proxy Server.** NanoProxy is a SOCKS5 proxy Server that can be used to proxy network traffic for various
applications.
- [x] **HTTP proxy Server.** NanoProxy can now act as an HTTP proxy Server for forwarding HTTP requests.
- [x] **TOR support.** NanoProxy can be run with Tor support to provide anonymized network traffic (Docker only).
- [x] **IP Rotation with Tor.** NanoProxy allows for IP rotation using the Tor network, providing enhanced anonymity and
privacy by periodically changing exit nodes.
Expand Down Expand Up @@ -185,13 +208,13 @@ nanoproxy
You can also run NanoProxy using Docker. To do so, you can use the following command:

```shell
docker run -p 1080:1080 ghcr.io/ryanbekhen/nanoproxy:latest
docker run -p 1080:1080 -p 8080:8080 ghcr.io/ryanbekhen/nanoproxy:latest
```

You can also run NanoProxy behind Tor using the following command:

```shell
docker run --rm -e TOR_ENABLED=true -d --privileged --cap-add=NET_ADMIN --sysctl net.ipv6.conf.all.disable_ipv6=0 --sysctl net.ipv4.conf.all.src_valid_mark=1 -p 1080:1080 ghcr.io/ryanbekhen/nanoproxy-tor:latest
docker run --rm -e TOR_ENABLED=true -d --privileged --cap-add=NET_ADMIN --sysctl net.ipv6.conf.all.disable_ipv6=0 --sysctl net.ipv4.conf.all.src_valid_mark=1 -p 1080:1080 -p 8080:8080 ghcr.io/ryanbekhen/nanoproxy-tor:latest
```

## Configuration
Expand All @@ -201,6 +224,7 @@ desired values:

```text
ADDR=:1080
ADDR_HTTP=:8080
NETWORK=tcp
TZ=Asia/Jakarta
CLIENT_TIMEOUT=10s
Expand Down Expand Up @@ -229,17 +253,24 @@ The following table lists the available configuration options:
| Name | Description | Default Value |
|-----------------------|-----------------------------------------------------------------|---------------|
| ADDR | The address to listen on. | `:1080` |
| ADDR_HTTP | The address to listen on for HTTP requests. | `:8080` |
| NETWORK | The network to listen on. (tcp, tcp4, tcp6) | `tcp` |
| TZ | The timezone to use. | `Local` |
| CLIENT_TIMEOUT | The timeout for connecting to the destination server. | `10s` |
| CLIENT_TIMEOUT | The timeout for connecting to the destination Server. | `10s` |
| DNS_TIMEOUT | The timeout for DNS resolution. | `10s` |
| CREDENTIALS | The credentials to use for authentication. | `""` |
| TOR_ENABLED | Enable Tor support. (works only on Docker) | `false` |
| TOR_IDENTITY_INTERVAL | The interval to change the Tor identity. (works only on Docker) | `10m` |ß
| TOR_IDENTITY_INTERVAL | The interval to change the Tor identity. (works only on Docker) | `10m` |

- **ADDR_HTTP**: By default, NanoProxy listens for HTTP proxy traffic on `:8080`. You can set this address to any host:
port combination for custom setups.
- **CREDENTIALS**: When enabled, both SOCKS5 and HTTP Proxy requests are authenticated using the credentials provided in
this field. This supports `username:password` pairs.

## Logging

NanoProxy logs all requests and responses to the standard output. You can use the `journalctl` command to view the logs:
NanoProxy logs all requests and responses to standard output, including SOCKS5 and HTTP Proxy traffic. To view logs for
HTTP Proxy requests, use the `journalctl` command:

```shell
journalctl -u nanoproxy
Expand All @@ -250,15 +281,43 @@ journalctl -u nanoproxy
To test the proxy using cURL, you can use the `-x` flag followed by the proxy URL. For example, to fetch the Google
homepage using the proxy running on `localhost:8080`, use the following command:

### SOCKS5 Proxy

```shell
curl -x socks5://localhost:1080 https://google.com
```

Replace localhost:8080 with the actual address and port where your NanoProxy instance is running. This command instructs
cURL to use the specified proxy for the request, allowing you to see the request and response through the proxy server.
### HTTP Proxy

```shell
curl -x localhost:8080 https://google.com
```

If credentials are enabled for HTTP Proxy, use the `-U` flag to supply the username and password:

```shell
curl -x http://localhost:8080 -U username:password https://example.com
```

In both cases, replace `localhost:8080` with the actual address and port where your NanoProxy instance is running.

## Authentication for HTTP Proxy

If authentication is enabled (via the `CREDENTIALS` configuration), the HTTP Proxy requires clients to include the
`Proxy-Authorization` header in their requests. The header must use the following format:

```http
Proxy-Authorization: Basic <base64_encoded("username:password")>
```

For example, to use the HTTP Proxy with curl and provide authentication, run:

```shell
curl -x http://localhost:8080 -U username:password https://example.com
```

Remember that you can adjust the proxy address and port as needed based on your setup. This is a convenient way to
verify that NanoProxy is correctly intercepting and forwarding the traffic.
If authentication fails or is not provided, the proxy will return `407 Proxy Authentication Required` along with the
appropriate `Proxy-Authenticate` header.

## Contributions

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/caarlos0/env/v10 v10.0.0
github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.30.0
golang.org/x/crypto v0.31.0
golang.org/x/net v0.32.0
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
74 changes: 58 additions & 16 deletions nanoproxy.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
//go:build !test

package main

import (
"github.com/caarlos0/env/v10"
"github.com/rs/zerolog"
"github.com/ryanbekhen/nanoproxy/pkg/config"
"github.com/ryanbekhen/nanoproxy/pkg/credential"
"github.com/ryanbekhen/nanoproxy/pkg/httpproxy"
"github.com/ryanbekhen/nanoproxy/pkg/resolver"
"github.com/ryanbekhen/nanoproxy/pkg/socks5"
"github.com/ryanbekhen/nanoproxy/pkg/tor"
"net"
"net/http"
"os"
"strings"
"time"
Expand All @@ -28,28 +31,40 @@ func main() {
time.Local = loc
}

socks5Config := socks5.Config{
Logger: &logger,
DestConnTimeout: cfg.DestTimeout,
ClientConnTimeout: cfg.ClientTimeout,
Resolver: &socks5.DNSResolver{},
}

credentials := socks5.StaticCredentialStore{}
credentials := credential.StaticCredentialStore{}
for _, cred := range cfg.Credentials {
credArr := strings.Split(cred, ":")
if len(credArr) != 2 {
logger.Fatal().Msgf("Invalid credential: %s", cred)
}

credentials[credArr[0]] = credArr[1]
}
if len(credentials) > 0 {
socks5Config.Credentials = credentials

dnsResolver := &resolver.DNSResolver{}

httpConfig := httpproxy.Config{
Credentials: credentials,
Logger: &logger,
DestConnTimeout: cfg.DestTimeout,
ClientConnTimeout: cfg.ClientTimeout,
Dial: net.Dial,
Resolver: dnsResolver,
}

httpServer := httpproxy.New(&httpConfig)

socks5Config := socks5.Config{
Logger: &logger,
DestConnTimeout: cfg.DestTimeout,
ClientConnTimeout: cfg.ClientTimeout,
Resolver: dnsResolver,
}

if cfg.TorEnabled {
torDialer := &tor.DefaultDialer{}
socks5Config.Dial = torDialer.Dial
httpConfig.Dial = torDialer.Dial
logger.Info().Msg("Tor mode enabled")

torController := tor.NewTorController(torDialer)
Expand All @@ -62,10 +77,37 @@ func main() {
}()
}

if len(credentials) > 0 {
authenticator := &socks5.UserPassAuthenticator{
Credentials: credentials,
}
socks5Config.Authentication = append(socks5Config.Authentication, authenticator)
}

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 {
logger.Fatal().Msg(err.Error())
}
go func() {
logger.Info().Msgf("Starting HTTP proxy server on %s://%s", cfg.Network, cfg.ADDRHttp)

server := &http.Server{
Addr: cfg.ADDRHttp,
Handler: httpServer,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}

if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Fatal().Msg(err.Error())
}
}()

go func() {
logger.Info().Msgf("Starting SOCKS5 server on %s://%s", cfg.Network, cfg.ADDR)
if err := sock5Server.ListenAndServe(cfg.Network, cfg.ADDR); err != nil {
logger.Fatal().Msg(err.Error())
}
}()

select {}
}
1 change: 1 addition & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type Config struct {
Timezone string `env:"TZ" envDefault:"Local"`
Network string `env:"NETWORK" envDefault:"tcp"`
ADDR string `env:"ADDR" envDefault:":1080"`
ADDRHttp string `env:"ADDR_HTTP" envDefault:":8080"`
Credentials []string `env:"CREDENTIALS" envSeparator:","`
ClientTimeout time.Duration `env:"CLIENT_TIMEOUT" envDefault:"15s"`
DestTimeout time.Duration `env:"DEST_TIMEOUT" envDefault:"15s"`
Expand Down
4 changes: 2 additions & 2 deletions pkg/socks5/credentials.go → pkg/credential/credentials.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package socks5
package credential

import (
"golang.org/x/crypto/bcrypt"
)

type CredentialStore interface {
type Store interface {
Valid(user, password string) bool
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package socks5
package credential

import (
"github.com/stretchr/testify/assert"
"testing"
)

func Test_CredentialStore_Valid(t *testing.T) {
var s CredentialStore
s = StaticCredentialStore{
s := StaticCredentialStore{
"foo": "$2y$05$Xr4Vj6wbsCuf70.Fif2guuX8Ez97GB0VysyCTRL2EMkIikCpY/ugi",
}
assert.True(t, s.Valid("foo", "bar"))
Expand Down
Loading
Loading