Skip to content

Commit ccb245c

Browse files
authored
Add basic auth to socks5 forwarding (#11)
1 parent fef2c69 commit ccb245c

File tree

4 files changed

+90
-55
lines changed

4 files changed

+90
-55
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Added
9+
- New `--socks` option which allows forwarding requests to a SOCKS5 proxy.
10+
- New `--socks-auth` option which allows adding basic authentication details to forwarded SOCKS5 proxy requests.
811

912
## [v1.2.0] - 2024-07-15
1013
### Added

README.md

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ Code based on the guide here: <https://medium.com/@mlowicki/http-s-proxy-in-gola
77

88
## Features
99

10-
- HTTP and HTTPS.
11-
- Can choose which port to run on.
12-
- Can specify paths to certificate and private key file to use.
13-
- Logs each proxied connection.
14-
- Log options can be supplied using `glog`.
15-
- Can choose the log verbosity with the `-v` flag.
16-
- Can choose to log to a file.
17-
- Basic authentication.
18-
- Can log request headers.
19-
- Can log failed authentication attempt details.
20-
- Printing version number.
21-
- Tunnel HTTP proxy to socks5 proxy
10+
- HTTP and HTTPS
11+
- Can choose which port to run on
12+
- Can specify paths to certificate and private key file to use
13+
- Logs each proxied connection
14+
- Log options can be supplied using `glog`
15+
- Can choose the log verbosity with the `-v` flag
16+
- Can choose to log to a file
17+
- Basic authentication
18+
- Can log request headers
19+
- Can log failed authentication attempt details
20+
- Printing version number
21+
- Tunnelling HTTP proxy to SOCKS5 proxy
2222

2323
## Install
2424

@@ -49,10 +49,6 @@ You can run the binary directly:
4949

5050
```bash
5151
./simple-proxy
52-
53-
# tunnel the http proxy to socks5 proxy
54-
# client -> localhost:7990 [http proxy] -> 127.0.0.1:7890 [socks5 proxy] -> server
55-
./simple-proxy -port 7990 -socks 127.0.0.1:7890
5652
```
5753

5854
## Windows
@@ -91,10 +87,12 @@ Usage of simple-proxy:
9187
log to standard error instead of files
9288
-port string
9389
proxy port to listen on (default "8888")
94-
-socks5 string
95-
proxy tunnel the http requests to a socks5 proxy (default "", feature off)
9690
-protocol string
9791
proxy protocol (http or https) (default "http")
92+
-socks5 string
93+
SOCKS5 proxy for tunneling, not used if not provided
94+
-socks5-auth string
95+
basic auth for socks5, format 'username:password', no auth if not provided
9896
-stderrthreshold value
9997
logs at or above this threshold go to stderr
10098
-timeout int

main.go

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"time"
1212

1313
"github.com/golang/glog"
14-
my_proxy "github.com/jthomperoo/simple-proxy/proxy"
14+
"github.com/jthomperoo/simple-proxy/proxy"
1515
)
1616

1717
var (
@@ -37,7 +37,9 @@ func main() {
3737
var port string
3838
flag.StringVar(&port, "port", "8888", "proxy port to listen on")
3939
var socks5 string
40-
flag.StringVar(&socks5, "socks5", "", "SOCKS5 proxy for tunneling")
40+
flag.StringVar(&socks5, "socks5", "", "SOCKS5 proxy for tunneling, not used if not provided")
41+
var socks5Auth string
42+
flag.StringVar(&socks5Auth, "socks5-auth", "", "basic auth for socks5, format 'username:password', no auth if not provided")
4143
var certPath string
4244
flag.StringVar(&certPath, "cert", "", "path to cert file")
4345
var keyPath string
@@ -65,26 +67,42 @@ func main() {
6567
glog.Fatalf("If using HTTPS protocol --cert and --key are required\n")
6668
}
6769

68-
my_proxy.Socks5 = socks5
70+
var socks5Forward *proxy.Socks5Forward
71+
if socks5 != "" {
72+
socks5Forward = &proxy.Socks5Forward{
73+
Address: socks5,
74+
}
75+
if socks5Auth != "" {
76+
parts := strings.Split(socks5Auth, ":")
77+
if len(parts) < 2 {
78+
glog.Fatalf("Invalid socks5 basic auth provided, must be in format 'username:password', auth: %s\n", basicAuth)
79+
}
80+
81+
socks5Forward.Username = &parts[0]
82+
socks5Forward.Password = &parts[1]
83+
}
84+
}
6985

7086
var handler http.Handler
7187
if basicAuth == "" {
72-
handler = &my_proxy.ProxyHandler{
73-
Timeout: time.Duration(timeoutSecs) * time.Second,
74-
LogAuth: logAuth,
75-
LogHeaders: logHeaders,
88+
handler = &proxy.ProxyHandler{
89+
Timeout: time.Duration(timeoutSecs) * time.Second,
90+
LogAuth: logAuth,
91+
LogHeaders: logHeaders,
92+
Socks5Forward: socks5Forward,
7693
}
7794
} else {
7895
parts := strings.Split(basicAuth, ":")
7996
if len(parts) < 2 {
8097
glog.Fatalf("Invalid basic auth provided, must be in format 'username:password', auth: %s\n", basicAuth)
8198
}
82-
handler = &my_proxy.ProxyHandler{
83-
Timeout: time.Duration(timeoutSecs) * time.Second,
84-
Username: &parts[0],
85-
Password: &parts[1],
86-
LogAuth: logAuth,
87-
LogHeaders: logHeaders,
99+
handler = &proxy.ProxyHandler{
100+
Timeout: time.Duration(timeoutSecs) * time.Second,
101+
Username: &parts[0],
102+
Password: &parts[1],
103+
LogAuth: logAuth,
104+
LogHeaders: logHeaders,
105+
Socks5Forward: socks5Forward,
88106
}
89107
}
90108

proxy/proxy.go

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,28 @@ import (
99
"time"
1010

1111
"github.com/golang/glog"
12-
net_proxy "golang.org/x/net/proxy"
12+
netProxy "golang.org/x/net/proxy"
1313
)
1414

15-
// Using a socks5 proxy to tunnel the HTTP requests, e.g., 127.0.0.1:7890
16-
var Socks5 = ""
17-
1815
func NewProxyHandler(timeoutSeconds int) *ProxyHandler {
1916
return &ProxyHandler{
2017
Timeout: time.Duration(timeoutSeconds) * time.Second,
2118
}
2219
}
2320

2421
type ProxyHandler struct {
25-
Timeout time.Duration
26-
Username *string
27-
Password *string
28-
LogAuth bool
29-
LogHeaders bool
22+
Timeout time.Duration
23+
Username *string
24+
Password *string
25+
LogAuth bool
26+
LogHeaders bool
27+
Socks5Forward *Socks5Forward
28+
}
29+
30+
type Socks5Forward struct {
31+
Address string
32+
Username *string
33+
Password *string
3034
}
3135

3236
func (p *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -52,52 +56,65 @@ func (p *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
5256
}
5357
}
5458
if r.Method == http.MethodConnect {
55-
handleTunneling(w, r, p.Timeout)
59+
handleTunneling(w, r, p.Timeout, p.Socks5Forward)
5660
} else {
5761
handleHTTP(w, r)
5862
}
5963
}
6064

61-
func handleTunneling(w http.ResponseWriter, r *http.Request, timeout time.Duration) {
62-
var dest_conn net.Conn
65+
func handleTunneling(w http.ResponseWriter, r *http.Request, timeout time.Duration, socks5Forward *Socks5Forward) {
66+
var destConn net.Conn
6367
var err error
6468

65-
// Check if the socks5 proxy is set
66-
// Then tunnel to socks5 proxy
67-
if Socks5 == "" {
68-
dest_conn, err = net.DialTimeout("tcp", r.Host, timeout)
69+
if socks5Forward == nil {
70+
destConn, err = net.DialTimeout("tcp", r.Host, timeout)
6971
} else {
70-
var socks5_dailer net_proxy.Dialer
71-
socks5_dailer, err = net_proxy.SOCKS5("tcp", Socks5, nil, &net.Dialer{
72+
var socks5Auth *netProxy.Auth
73+
if socks5Forward.Username != nil && socks5Forward.Password != nil {
74+
socks5Auth = &netProxy.Auth{
75+
User: *socks5Forward.Username,
76+
Password: *socks5Forward.Password,
77+
}
78+
}
79+
80+
var socks5Dialer netProxy.Dialer
81+
socks5Dialer, err = netProxy.SOCKS5("tcp", socks5Forward.Address, socks5Auth, &net.Dialer{
7282
Timeout: timeout,
7383
KeepAlive: 30 * time.Second,
7484
})
85+
7586
if err != nil {
76-
glog.Errorf("Failed to dail socks5 proxy %s, %s\n", Socks5, err.Error())
87+
glog.Errorf("Failed to dial socks5 proxy %s, %s\n", socks5Forward.Address, err.Error())
7788
http.Error(w, err.Error(), http.StatusServiceUnavailable)
7889
return
7990
}
80-
dest_conn, err = socks5_dailer.Dial("tcp", r.Host)
91+
92+
destConn, err = socks5Dialer.Dial("tcp", r.Host)
8193
}
94+
8295
if err != nil {
8396
glog.Errorf("Failed to dial host, %s\n", err.Error())
8497
http.Error(w, err.Error(), http.StatusServiceUnavailable)
8598
return
8699
}
100+
87101
w.WriteHeader(http.StatusOK)
102+
88103
hijacker, ok := w.(http.Hijacker)
89104
if !ok {
90105
glog.Errorln("Attempted to hijack connection that does not support it")
91106
http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
92107
return
93108
}
94-
client_conn, _, err := hijacker.Hijack()
109+
110+
clientConn, _, err := hijacker.Hijack()
95111
if err != nil {
96112
glog.Errorf("Failed to hijack connection, %s\n", err.Error())
97113
http.Error(w, err.Error(), http.StatusServiceUnavailable)
98114
}
99-
go transfer(dest_conn, client_conn)
100-
go transfer(client_conn, dest_conn)
115+
116+
go transfer(destConn, clientConn)
117+
go transfer(clientConn, destConn)
101118
}
102119

103120
func transfer(destination io.WriteCloser, source io.ReadCloser) {
@@ -139,7 +156,6 @@ func proxyBasicAuth(r *http.Request) (username, password string, ok bool) {
139156
// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
140157
func parseBasicAuth(auth string) (username, password string, ok bool) {
141158
const prefix = "Basic "
142-
// Case insensitive prefix match. See Issue 22736.
143159
if len(auth) < len(prefix) || !equalFold(auth[:len(prefix)], prefix) {
144160
return
145161
}

0 commit comments

Comments
 (0)