Skip to content

Commit 2d60cce

Browse files
added basic sniffing functionality
1 parent 95f7a34 commit 2d60cce

File tree

7 files changed

+315
-35
lines changed

7 files changed

+315
-35
lines changed

README.md

Lines changed: 163 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
- [Usage](#usage)
1616
- [Configuration via CLI flags](#configuration-via-cli-flags)
1717
- [Configuration via YAML file](#configuration-via-yaml-file)
18-
- [Transparent proxy](#usage)
18+
- [Transparent proxy](#transparent-proxy)
1919
- [redirect (via NAT and SO_ORIGINAL_DST)](#redirect-via-nat-and-so_original_dst)
2020
- [tproxy (via MANGLE and IP_TRANSPARENT)](#tproxy-via-mangle-and-ip_transparent)
21+
- [Traffic sniffing](#traffic-sniffing)
2122
- [Links](#links)
2223
- [License](#license)
2324

@@ -51,6 +52,9 @@ Specify http server in proxy configuration of Postman
5152
- **Transparent proxy**
5253
Supports `redirect` (SO_ORIGINAL_DST) and `tproxy` (IP_TRANSPARENT) modes
5354

55+
- **Traffic sniffing**
56+
Proxy is able to parse HTTP headers and TLS handshake metadata
57+
5458
- **DNS Leak Protection**
5559
DNS resolution occurs on SOCKS5 server side.
5660

@@ -82,7 +86,7 @@ You can download the binary for your platform from [Releases](https://github.com
8286
Example:
8387

8488
```shell
85-
HPTS_RELEASE=v1.6.1; wget -v https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases/download/$HPTS_RELEASE/gohpts-$HPTS_RELEASE-linux-amd64.tar.gz -O gohpts && tar xvzf gohpts && mv -f gohpts-$HPTS_RELEASE-linux-amd64 gohpts && ./gohpts -h
89+
HPTS_RELEASE=v1.7.0; wget -v https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases/download/$HPTS_RELEASE/gohpts-$HPTS_RELEASE-linux-amd64.tar.gz -O gohpts && tar xvzf gohpts && mv -f gohpts-$HPTS_RELEASE-linux-amd64 gohpts && ./gohpts -h
8690
```
8791

8892
Alternatively, you can install it using `go install` command (requires Go [1.24](https://go.dev/doc/install) or later):
@@ -119,32 +123,36 @@ GitHub: https://github.com/shadowy-pycoder/go-http-proxy-to-socks
119123
Usage: gohpts [OPTIONS]
120124
Options:
121125
-h Show this help message and exit.
122-
-D Run as a daemon (provide -logfile to see logs)
126+
-D Run as a daemon (provide -logfile to see logs)
123127
-M value
124-
Transparent proxy mode: [redirect tproxy]
128+
Transparent proxy mode: [redirect tproxy]
125129
-T string
126-
Address of transparent proxy server (no HTTP)
130+
Address of transparent proxy server (no HTTP)
127131
-U string
128-
User for HTTP proxy (basic auth). This flag invokes prompt for password (not echoed to terminal)
132+
User for HTTP proxy (basic auth). This flag invokes prompt for password (not echoed to terminal)
129133
-c string
130-
Path to certificate PEM encoded file
131-
-d Show logs in DEBUG mode
134+
Path to certificate PEM encoded file
135+
-d Show logs in DEBUG mode
132136
-f string
133-
Path to server configuration file in YAML format
134-
-j Show logs in JSON format
137+
Path to server configuration file in YAML format
138+
-j Show logs in JSON format
135139
-k string
136-
Path to private key PEM encoded file
140+
Path to private key PEM encoded file
137141
-l string
138-
Address of HTTP proxy server (default "127.0.0.1:8080")
142+
Address of HTTP proxy server (default "127.0.0.1:8080")
139143
-logfile string
140-
Log file path (Default: stdout)
144+
Log file path (Default: stdout)
141145
-s string
142-
Address of SOCKS5 proxy server (default "127.0.0.1:1080")
146+
Address of SOCKS5 proxy server (default "127.0.0.1:1080")
147+
-sniff
148+
Enable traffic sniffing for HTTP and TLS
149+
-snifflog string
150+
Sniffed traffic log file path (Default: the same as -logfile)
143151
-t string
144-
Address of transparent proxy server (it starts along with HTTP proxy server)
152+
Address of transparent proxy server (it starts along with HTTP proxy server)
145153
-u string
146-
User for SOCKS5 proxy authentication. This flag invokes prompt for password (not echoed to terminal)
147-
-v print version
154+
User for SOCKS5 proxy authentication. This flag invokes prompt for password (not echoed to terminal)
155+
-v print version
148156
```
149157

150158
### Configuration via CLI flags
@@ -397,6 +405,144 @@ ip netns del ns-client
397405
ip link del veth1
398406
```
399407

408+
## Traffic sniffing
409+
410+
[[Back]](#table-of-contents)
411+
412+
`GoHPTS` proxy allows one to capture and monitor traffic that goes through the service. This procces is known as `traffic sniffing`, `packet sniffing` or just `sniffing`. In particular, proxy tries to identify whether it is a plain text (HTTP) or TLS traffic, and after identification is done, it parses request/response metadata and writes it to the file or console. In the case of `GoHTPS` proxy a parsed metadata looks like the following (TLS Handshake):
413+
414+
```json
415+
[
416+
{
417+
"connection": {
418+
"tproxy_mode": "redirect",
419+
"src_local": "127.0.0.1:8888",
420+
"src_remote": "192.168.0.107:51142",
421+
"dst_local": "127.0.0.1:56256",
422+
"dst_remote": "127.0.0.1:1080",
423+
"original_dst": "216.58.209.206:443"
424+
}
425+
},
426+
{
427+
"tls_request": {
428+
"sni": "www.youtube.com",
429+
"type": "Client hello (1)",
430+
"version": "TLS 1.2 (0x0303)",
431+
"session_id": "2670a6779b4346e5e84d46890ad2aaf7a53b08adcfe0c9f6868c2d9882242e39",
432+
"cipher_suites": [
433+
"TLS_AES_128_GCM_SHA256 (0x1301)",
434+
"TLS_CHACHA20_POLY1305_SHA256 (0x1303)",
435+
"TLS_AES_256_GCM_SHA384 (0x1302)",
436+
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)",
437+
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)",
438+
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca9)",
439+
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca8)",
440+
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)",
441+
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)",
442+
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a)",
443+
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009)",
444+
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)",
445+
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)",
446+
"TLS_RSA_WITH_AES_128_GCM_SHA256 (0x9c)",
447+
"TLS_RSA_WITH_AES_256_GCM_SHA384 (0x9d)",
448+
"TLS_RSA_WITH_AES_128_CBC_SHA (0x2f)",
449+
"TLS_RSA_WITH_AES_256_CBC_SHA (0x35)"
450+
],
451+
"extensions": [
452+
"server_name (0)",
453+
"extended_master_secret (23)",
454+
"renegotiation_info (65281)",
455+
"supported_groups (10)",
456+
"ec_point_formats (11)",
457+
"session_ticket (35)",
458+
"application_layer_protocol_negotiation (16)",
459+
"status_request (5)",
460+
"delegated_credential (34)",
461+
"signed_certificate_timestamp (18)",
462+
"key_share (51)",
463+
"supported_versions (43)",
464+
"signature_algorithms (13)",
465+
"psk_key_exchange_modes (45)",
466+
"record_size_limit (28)",
467+
"compress_certificate (27)",
468+
"encrypted_client_hello (65037)"
469+
],
470+
"alpn": ["h2", "http/1.1"]
471+
}
472+
},
473+
{
474+
"tls_response": {
475+
"type": "Server hello (2)",
476+
"version": "TLS 1.2 (0x0303)",
477+
"session_id": "2670a6779b4346e5e84d46890ad2aaf7a53b08adcfe0c9f6868c2d9882242e39",
478+
"cipher_suite": "TLS_AES_128_GCM_SHA256 (0x1301)",
479+
"extensions": ["key_share (51)", "supported_versions (43)"],
480+
"supported_version": "TLS 1.3 (0x0304)"
481+
}
482+
}
483+
]
484+
```
485+
486+
And HTTP request with curl:
487+
488+
```json
489+
[
490+
{
491+
"connection": {
492+
"tproxy_mode": "redirect",
493+
"src_local": "127.0.0.1:8888",
494+
"src_remote": "192.168.0.107:45736",
495+
"dst_local": "127.0.0.1:37640",
496+
"dst_remote": "127.0.0.1:1080",
497+
"original_dst": "96.7.128.198:80"
498+
}
499+
},
500+
{
501+
"http_request": {
502+
"host": "example.com",
503+
"uri": "/",
504+
"method": "GET",
505+
"proto": "HTTP/1.1",
506+
"header": {
507+
"Accept": ["*/*"],
508+
"My": ["Header"],
509+
"User-Agent": ["curl/7.81.0"]
510+
}
511+
}
512+
},
513+
{
514+
"http_response": {
515+
"proto": "HTTP/1.1",
516+
"status": "200 OK",
517+
"content-length": 1256,
518+
"header": {
519+
"Cache-Control": ["max-age=2880"],
520+
"Connection": ["keep-alive"],
521+
"Content-Length": ["1256"],
522+
"Content-Type": ["text/html"],
523+
"Date": ["Tue, 17 Jun 2025 14:43:24 GMT"],
524+
"Etag": ["\"84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134\""],
525+
"Last-Modified": ["Mon, 13 Jan 2025 20:11:20 GMT"]
526+
}
527+
}
528+
}
529+
]
530+
```
531+
532+
Usage as simple as specifying `-sniff` flag along with regular flags
533+
534+
```shell
535+
gohpts -d -t 8888 -M redirect -sniff
536+
```
537+
538+
You can also specify a file to which write sniffed traffic:
539+
540+
```shell
541+
gohpts -d -sniff -snifflog ~/sniff.log
542+
```
543+
544+
Please note that for now sniffing only visible with `-d` flag, it may change in the future.
545+
400546
## Links
401547

402548
Learn more about transparent proxies by visiting the following links:

cmd/gohpts/cli.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ func root(args []string) error {
5959
flags.StringVar(&conf.LogFilePath, "logfile", "", "Log file path (Default: stdout)")
6060
flags.BoolVar(&conf.Debug, "d", false, "Show logs in DEBUG mode")
6161
flags.BoolVar(&conf.Json, "j", false, "Show logs in JSON format")
62+
flags.BoolVar(&conf.Sniff, "sniff", false, "Enable traffic sniffing for HTTP and TLS")
63+
flags.StringVar(&conf.SniffLogFile, "snifflog", "", "Sniffed traffic log file path (Default: the same as -logfile)")
6264
flags.BoolFunc("v", "print version", func(flagValue string) error {
6365
fmt.Println(gohpts.Version)
6466
os.Exit(0)
@@ -86,7 +88,7 @@ func root(args []string) error {
8688
if seen["T"] {
8789
for _, da := range []string{"U", "c", "k", "l"} {
8890
if seen[da] {
89-
return fmt.Errorf("-T flag only works with -s, -u, -f, -M, -d, -D, -logfile and -j flags")
91+
return fmt.Errorf("-T flag only works with -s, -u, -f, -M, -d, -D, -logfile, -sniff, -snifflog and -j flags")
9092
}
9193
}
9294
if !seen["M"] {
@@ -102,9 +104,9 @@ func root(args []string) error {
102104
for _, da := range []string{"s", "u", "U", "c", "k", "l"} {
103105
if seen[da] {
104106
if runtime.GOOS == tproxyOS {
105-
return fmt.Errorf("-f flag only works with -t, -T, -M, -d, -D, -logfile and -j flags")
107+
return fmt.Errorf("-f flag only works with -t, -T, -M, -d, -D, -logfile, -sniff, -snifflog and -j flags")
106108
}
107-
return fmt.Errorf("-f flag only works with -d, -D, -logfile and -j flags")
109+
return fmt.Errorf("-f flag only works with -d, -D, -logfile, -sniff, -snifflog and -j flags")
108110
}
109111
}
110112
}
@@ -131,6 +133,16 @@ func root(args []string) error {
131133
conf.ServerPass = string(bytepw)
132134
fmt.Print("\033[2K\r")
133135
}
136+
if seen["sniff"] {
137+
if !seen["d"] {
138+
return fmt.Errorf("Traffic sniffing requires debug mode")
139+
}
140+
}
141+
if seen["snifflog"] {
142+
if !seen["sniff"] {
143+
return fmt.Errorf("-snifflog only works with -sniff flag")
144+
}
145+
}
134146

135147
if *daemon {
136148
if os.Getenv("GOHPTS_DAEMON") != "1" {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.24.1
55
require (
66
github.com/goccy/go-yaml v1.18.0
77
github.com/rs/zerolog v1.34.0
8+
github.com/shadowy-pycoder/mshark v0.0.4
89
golang.org/x/net v0.40.0
910
golang.org/x/sys v0.33.0
1011
golang.org/x/term v0.32.0

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
24
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
35
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
46
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@@ -8,9 +10,15 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
810
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
911
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
1012
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
13+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
14+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1115
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
1216
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
1317
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
18+
github.com/shadowy-pycoder/mshark v0.0.4 h1:2yw6am1jt6n1GPHdLfFU1oDajv+zQ/23V0l0imFAeJY=
19+
github.com/shadowy-pycoder/mshark v0.0.4/go.mod h1:fRWGQuU4BFjz9pTfrvwIT2AtmWWd99PEvdlgv+24vTE=
20+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
21+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
1422
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
1523
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
1624
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -20,3 +28,5 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
2028
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
2129
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
2230
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
31+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
32+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)