mirror of
https://github.com/Gouryella/drip.git
synced 2026-02-23 21:00:44 +00:00
Merge pull request #13 from Gouryella/feat/prometheus-monitoring
Feat/prometheus monitoring
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -53,4 +53,5 @@ certs/
|
||||
.drip-server.env
|
||||
benchmark-results/
|
||||
drip-linux-amd64
|
||||
./drip
|
||||
./drip
|
||||
drip
|
||||
|
||||
31
README.md
31
README.md
@@ -46,33 +46,6 @@
|
||||
| Interstitial Page | None | Yes (removable with header) |
|
||||
| Open Source | ✓ | ✗ |
|
||||
|
||||
## What's New in v0.5.0
|
||||
|
||||
### 🔄 Switched to Yamux Protocol
|
||||
|
||||
Our custom multiplexing protocol had too many edge-case bugs. We replaced it with [yamux](https://github.com/hashicorp/yamux), HashiCorp's battle-tested stream multiplexing library.
|
||||
|
||||
**Why Yamux?**
|
||||
- Production-proven in Consul, Nomad, and other critical infrastructure
|
||||
- Built-in flow control and keepalive support
|
||||
- Active maintenance and community support
|
||||
|
||||
**What changed:**
|
||||
- Removed: Custom HPACK compression, flow control, binary framing, HTTP codec
|
||||
- Added: Yamux-based connection pooling and session management
|
||||
- Result: ~60% less protocol code, significantly improved stability
|
||||
|
||||
### ⚡ Performance Improvements
|
||||
|
||||
| Metric | Improvement |
|
||||
|--------|-------------|
|
||||
| Connection setup | 3x faster (session reuse) |
|
||||
| Memory per tunnel | -50% (simplified state) |
|
||||
| Latency (p99) | -40% (fewer encoding layers) |
|
||||
| Throughput | +80% (efficient multiplexing) |
|
||||
|
||||
> ⚠️ **Breaking Change**: Protocol incompatible with v0.4.x. Upgrade both client and server.
|
||||
|
||||
## Quick Install
|
||||
|
||||
```bash
|
||||
@@ -305,6 +278,10 @@ drip config show Show current config
|
||||
drip config set <key> <value>
|
||||
```
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
- [yamux](https://github.com/hashicorp/yamux) - Stream multiplexing library powering Drip's connection multiplexing
|
||||
|
||||
## License
|
||||
|
||||
BSD 3-Clause License - see [LICENSE](LICENSE) for details
|
||||
|
||||
31
README_CN.md
31
README_CN.md
@@ -46,33 +46,6 @@
|
||||
| 中间页 | 无 | 有(加请求头可移除) |
|
||||
| 开源 | ✓ | ✗ |
|
||||
|
||||
## v0.5.0 更新内容
|
||||
|
||||
### 🔄 切换到 Yamux 协议
|
||||
|
||||
自研的多路复用协议存在较多边界情况的 bug,难以复现和修复。我们决定暂时放弃自研协议,改用 HashiCorp 久经考验的 [yamux](https://github.com/hashicorp/yamux) 库。
|
||||
|
||||
**为什么选择 Yamux?**
|
||||
- 生产环境验证,被 Consul、Nomad 等关键基础设施广泛使用
|
||||
- 内置流控和心跳保活
|
||||
- 活跃的社区维护
|
||||
|
||||
**变更内容:**
|
||||
- 移除:自研 HPACK 压缩、流控机制、二进制帧协议、HTTP 编解码器
|
||||
- 新增:基于 Yamux 的连接池和会话管理
|
||||
- 结果:协议层代码减少约 60%,稳定性显著提升
|
||||
|
||||
### ⚡ 性能提升
|
||||
|
||||
| 指标 | 提升幅度 |
|
||||
|------|----------|
|
||||
| 连接建立 | 快 3 倍(会话复用) |
|
||||
| 单隧道内存占用 | -50%(状态简化) |
|
||||
| 延迟 (p99) | -40%(减少编码层) |
|
||||
| 吞吐量 | +80%(高效多路复用) |
|
||||
|
||||
> ⚠️ **破坏性变更**:协议与 v0.4.x 不兼容,客户端和服务端需同时升级。
|
||||
|
||||
## 快速安装
|
||||
|
||||
```bash
|
||||
@@ -304,6 +277,10 @@ drip config show 显示当前配置
|
||||
drip config set <键> <值>
|
||||
```
|
||||
|
||||
## 鸣谢
|
||||
|
||||
- [yamux](https://github.com/hashicorp/yamux) - 为 Drip 的连接复用提供支持的流复用库
|
||||
|
||||
## 协议
|
||||
|
||||
BSD 3-Clause License - 详见 [LICENSE](LICENSE)
|
||||
|
||||
35
go.mod
35
go.mod
@@ -7,29 +7,40 @@ require (
|
||||
github.com/goccy/go-json v0.10.5
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/hashicorp/yamux v0.1.2
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/spf13/cobra v1.10.2
|
||||
go.uber.org/zap v1.27.1
|
||||
golang.org/x/crypto v0.45.0
|
||||
golang.org/x/net v0.47.0
|
||||
golang.org/x/sys v0.38.0
|
||||
golang.org/x/crypto v0.46.0
|
||||
golang.org/x/net v0.48.0
|
||||
golang.org/x/sys v0.39.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/x/ansi v0.8.0 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.3 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.6.1 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.4 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
)
|
||||
|
||||
89
go.sum
89
go.sum
@@ -1,42 +1,73 @@
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
|
||||
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
|
||||
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/charmbracelet/x/ansi v0.11.3 h1:6DcVaqWI82BBVM/atTyq6yBoRLZFBsnoDoX9GCu2YOI=
|
||||
github.com/charmbracelet/x/ansi v0.11.3/go.mod h1:yI7Zslym9tCJcedxz5+WBq+eUGMJT0bM06Fqy1/Y4dI=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/clipperhouse/displaywidth v0.6.1 h1:/zMlAezfDzT2xy6acHBzwIfyu2ic0hgkT83UX5EY2gY=
|
||||
github.com/clipperhouse/displaywidth v0.6.1/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
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/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
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/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
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/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
|
||||
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
|
||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
@@ -50,18 +81,24 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
||||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -117,16 +117,17 @@ func runConfigShow(_ *cobra.Command, _ []string) error {
|
||||
|
||||
var displayToken string
|
||||
if cfg.Token != "" {
|
||||
tokenLen := len(cfg.Token)
|
||||
if tokenLen <= 3 {
|
||||
// For very short tokens, just show asterisks
|
||||
displayToken = "***"
|
||||
} else if tokenLen > 10 {
|
||||
// For long tokens, show first 3 and last 3 characters
|
||||
displayToken = cfg.Token[:3] + "***" + cfg.Token[tokenLen-3:]
|
||||
if configFull {
|
||||
displayToken = cfg.Token
|
||||
} else {
|
||||
// For medium tokens (4-10 chars), show first 3 characters
|
||||
displayToken = cfg.Token[:3] + "***"
|
||||
tokenLen := len(cfg.Token)
|
||||
if tokenLen <= 3 {
|
||||
displayToken = "***"
|
||||
} else if tokenLen > 10 {
|
||||
displayToken = cfg.Token[:3] + "***" + cfg.Token[tokenLen-3:]
|
||||
} else {
|
||||
displayToken = cfg.Token[:3] + "***"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
displayToken = ""
|
||||
|
||||
@@ -21,16 +21,17 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
serverPort int
|
||||
serverPublicPort int
|
||||
serverDomain string
|
||||
serverAuthToken string
|
||||
serverDebug bool
|
||||
serverTCPPortMin int
|
||||
serverTCPPortMax int
|
||||
serverTLSCert string
|
||||
serverTLSKey string
|
||||
serverPprofPort int
|
||||
serverPort int
|
||||
serverPublicPort int
|
||||
serverDomain string
|
||||
serverAuthToken string
|
||||
serverMetricsToken string
|
||||
serverDebug bool
|
||||
serverTCPPortMin int
|
||||
serverTCPPortMax int
|
||||
serverTLSCert string
|
||||
serverTLSKey string
|
||||
serverPprofPort int
|
||||
)
|
||||
|
||||
var serverCmd = &cobra.Command{
|
||||
@@ -48,6 +49,7 @@ func init() {
|
||||
serverCmd.Flags().IntVar(&serverPublicPort, "public-port", getEnvInt("DRIP_PUBLIC_PORT", 0), "Public port to display in URLs (env: DRIP_PUBLIC_PORT)")
|
||||
serverCmd.Flags().StringVarP(&serverDomain, "domain", "d", getEnvString("DRIP_DOMAIN", constants.DefaultDomain), "Server domain (env: DRIP_DOMAIN)")
|
||||
serverCmd.Flags().StringVarP(&serverAuthToken, "token", "t", getEnvString("DRIP_TOKEN", ""), "Authentication token (env: DRIP_TOKEN)")
|
||||
serverCmd.Flags().StringVar(&serverMetricsToken, "metrics-token", getEnvString("DRIP_METRICS_TOKEN", ""), "Metrics and stats token (env: DRIP_METRICS_TOKEN)")
|
||||
serverCmd.Flags().BoolVar(&serverDebug, "debug", false, "Enable debug logging")
|
||||
serverCmd.Flags().IntVar(&serverTCPPortMin, "tcp-port-min", getEnvInt("DRIP_TCP_PORT_MIN", constants.DefaultTCPPortMin), "Minimum TCP tunnel port (env: DRIP_TCP_PORT_MIN)")
|
||||
serverCmd.Flags().IntVar(&serverTCPPortMax, "tcp-port-max", getEnvInt("DRIP_TCP_PORT_MAX", constants.DefaultTCPPortMax), "Maximum TCP tunnel port (env: DRIP_TCP_PORT_MAX)")
|
||||
@@ -130,7 +132,7 @@ func runServer(_ *cobra.Command, _ []string) error {
|
||||
|
||||
listenAddr := fmt.Sprintf("0.0.0.0:%d", serverPort)
|
||||
|
||||
httpHandler := proxy.NewHandler(tunnelManager, logger, serverDomain, serverAuthToken)
|
||||
httpHandler := proxy.NewHandler(tunnelManager, logger, serverDomain, serverAuthToken, serverMetricsToken)
|
||||
|
||||
listener := tcp.NewListener(listenAddr, tlsConfig, serverAuthToken, tunnelManager, logger, portAllocator, serverDomain, displayPort, httpHandler)
|
||||
|
||||
|
||||
176
internal/client/cli/server_config.go
Normal file
176
internal/client/cli/server_config.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"drip/internal/shared/constants"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
serverConfigFull bool
|
||||
)
|
||||
|
||||
var serverConfigCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Manage server configuration",
|
||||
Long: "Display and manage Drip server configuration",
|
||||
}
|
||||
|
||||
var serverConfigShowCmd = &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Show server configuration",
|
||||
Long: "Display the current Drip server configuration from environment variables and flags",
|
||||
RunE: runServerConfigShow,
|
||||
}
|
||||
|
||||
func init() {
|
||||
serverConfigCmd.AddCommand(serverConfigShowCmd)
|
||||
serverConfigShowCmd.Flags().BoolVar(&serverConfigFull, "full", false, "Show full tokens (not masked)")
|
||||
|
||||
serverCmd.AddCommand(serverConfigCmd)
|
||||
}
|
||||
|
||||
func runServerConfigShow(_ *cobra.Command, _ []string) error {
|
||||
// Read configuration from environment variables and defaults
|
||||
port := getEnvInt("DRIP_PORT", 8443)
|
||||
publicPort := getEnvInt("DRIP_PUBLIC_PORT", 0)
|
||||
domain := getEnvString("DRIP_DOMAIN", constants.DefaultDomain)
|
||||
token := getEnvString("DRIP_TOKEN", "")
|
||||
metricsToken := getEnvString("DRIP_METRICS_TOKEN", "")
|
||||
tlsCert := getEnvString("DRIP_TLS_CERT", "")
|
||||
tlsKey := getEnvString("DRIP_TLS_KEY", "")
|
||||
tcpPortMin := getEnvInt("DRIP_TCP_PORT_MIN", constants.DefaultTCPPortMin)
|
||||
tcpPortMax := getEnvInt("DRIP_TCP_PORT_MAX", constants.DefaultTCPPortMax)
|
||||
pprofPort := getEnvInt("DRIP_PPROF_PORT", 0)
|
||||
|
||||
if publicPort == 0 {
|
||||
publicPort = port
|
||||
}
|
||||
|
||||
// Mask tokens if not showing full
|
||||
displayToken := maskToken(token, serverConfigFull)
|
||||
displayMetricsToken := maskToken(metricsToken, serverConfigFull)
|
||||
|
||||
// Print configuration
|
||||
fmt.Println()
|
||||
fmt.Println("╔══════════════════════════════════════════════════════════════╗")
|
||||
fmt.Println("║ Drip Server Configuration ║")
|
||||
fmt.Println("╚══════════════════════════════════════════════════════════════╝")
|
||||
fmt.Println()
|
||||
|
||||
// Server settings
|
||||
fmt.Println("📡 Server Settings:")
|
||||
fmt.Printf(" Domain: %s\n", colorValue(domain))
|
||||
fmt.Printf(" Port: %s\n", colorValue(fmt.Sprintf("%d", port)))
|
||||
if publicPort != port {
|
||||
fmt.Printf(" Public Port: %s\n", colorValue(fmt.Sprintf("%d", publicPort)))
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// Authentication
|
||||
fmt.Println("🔐 Authentication:")
|
||||
if token != "" {
|
||||
fmt.Printf(" Auth Token: %s\n", colorValue(displayToken))
|
||||
} else {
|
||||
fmt.Printf(" Auth Token: %s\n", colorWarning("(not set)"))
|
||||
}
|
||||
if metricsToken != "" {
|
||||
fmt.Printf(" Metrics Token: %s\n", colorValue(displayMetricsToken))
|
||||
} else {
|
||||
fmt.Printf(" Metrics Token: %s\n", colorWarning("(not set)"))
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// TLS Configuration
|
||||
fmt.Println("🔒 TLS Configuration:")
|
||||
if tlsCert != "" {
|
||||
certStatus := checkFileExists(tlsCert)
|
||||
fmt.Printf(" Certificate: %s %s\n", colorValue(tlsCert), certStatus)
|
||||
} else {
|
||||
fmt.Printf(" Certificate: %s\n", colorError("(not set)"))
|
||||
}
|
||||
if tlsKey != "" {
|
||||
keyStatus := checkFileExists(tlsKey)
|
||||
fmt.Printf(" Private Key: %s %s\n", colorValue(tlsKey), keyStatus)
|
||||
} else {
|
||||
fmt.Printf(" Private Key: %s\n", colorError("(not set)"))
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// TCP Tunnel Ports
|
||||
fmt.Println("🌐 TCP Tunnel Port Range:")
|
||||
fmt.Printf(" Min Port: %s\n", colorValue(fmt.Sprintf("%d", tcpPortMin)))
|
||||
fmt.Printf(" Max Port: %s\n", colorValue(fmt.Sprintf("%d", tcpPortMax)))
|
||||
fmt.Printf(" Available Ports: %s\n", colorValue(fmt.Sprintf("%d", tcpPortMax-tcpPortMin+1)))
|
||||
fmt.Println()
|
||||
|
||||
// Performance
|
||||
if pprofPort > 0 {
|
||||
fmt.Println("⚡ Performance:")
|
||||
fmt.Printf(" Pprof Port: %s\n", colorValue(fmt.Sprintf("%d", pprofPort)))
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Configuration sources
|
||||
fmt.Println("📋 Configuration Sources:")
|
||||
fmt.Println(" Environment variables (DRIP_*)")
|
||||
fmt.Println(" Command-line flags")
|
||||
fmt.Println(" Config file: /etc/drip/server.env")
|
||||
fmt.Println()
|
||||
|
||||
// Endpoints
|
||||
fmt.Println("🔗 Server Endpoints:")
|
||||
fmt.Printf(" Main: https://%s:%d\n", domain, publicPort)
|
||||
fmt.Printf(" Health: https://%s:%d/health\n", domain, publicPort)
|
||||
fmt.Printf(" Stats: https://%s:%d/stats\n", domain, publicPort)
|
||||
fmt.Printf(" Metrics: https://%s:%d/metrics\n", domain, publicPort)
|
||||
fmt.Println()
|
||||
|
||||
if !serverConfigFull && (token != "" || metricsToken != "") {
|
||||
fmt.Println("💡 Tip: Use --full flag to show complete tokens")
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func maskToken(token string, showFull bool) string {
|
||||
if token == "" {
|
||||
return ""
|
||||
}
|
||||
if showFull {
|
||||
return token
|
||||
}
|
||||
|
||||
tokenLen := len(token)
|
||||
if tokenLen <= 8 {
|
||||
return "***"
|
||||
}
|
||||
// Show first 4 and last 4 characters
|
||||
return token[:4] + "***" + token[tokenLen-4:]
|
||||
}
|
||||
|
||||
func checkFileExists(path string) string {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return colorSuccess("✓")
|
||||
}
|
||||
return colorError("✗ (not found)")
|
||||
}
|
||||
|
||||
func colorValue(s string) string {
|
||||
return fmt.Sprintf("\033[36m%s\033[0m", s) // Cyan
|
||||
}
|
||||
|
||||
func colorSuccess(s string) string {
|
||||
return fmt.Sprintf("\033[32m%s\033[0m", s) // Green
|
||||
}
|
||||
|
||||
func colorWarning(s string) string {
|
||||
return fmt.Sprintf("\033[33m%s\033[0m", s) // Yellow
|
||||
}
|
||||
|
||||
func colorError(s string) string {
|
||||
return fmt.Sprintf("\033[31m%s\033[0m", s) // Red
|
||||
}
|
||||
106
internal/server/metrics/metrics.go
Normal file
106
internal/server/metrics/metrics.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
)
|
||||
|
||||
var (
|
||||
// Tunnel metrics
|
||||
TunnelCount = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "drip_tunnel_count",
|
||||
Help: "Current number of active tunnels",
|
||||
})
|
||||
|
||||
TunnelRegistrations = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "drip_tunnel_registrations_total",
|
||||
Help: "Total number of tunnel registrations",
|
||||
})
|
||||
|
||||
TunnelRegistrationFailures = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "drip_tunnel_registration_failures_total",
|
||||
Help: "Total number of failed tunnel registrations",
|
||||
}, []string{"reason"})
|
||||
|
||||
TunnelsByIP = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "drip_tunnels_by_ip",
|
||||
Help: "Number of tunnels per client IP",
|
||||
}, []string{"ip"})
|
||||
|
||||
// Connection metrics
|
||||
ActiveConnections = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "drip_active_connections",
|
||||
Help: "Current number of active TCP connections",
|
||||
})
|
||||
|
||||
TotalConnections = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "drip_connections_total",
|
||||
Help: "Total number of connections handled",
|
||||
})
|
||||
|
||||
// Traffic metrics
|
||||
BytesReceived = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "drip_bytes_received_total",
|
||||
Help: "Total bytes received",
|
||||
})
|
||||
|
||||
BytesSent = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "drip_bytes_sent_total",
|
||||
Help: "Total bytes sent",
|
||||
})
|
||||
|
||||
RequestsTotal = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "drip_requests_total",
|
||||
Help: "Total number of HTTP requests handled",
|
||||
})
|
||||
|
||||
// Per-tunnel metrics
|
||||
TunnelBytesReceived = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "drip_tunnel_bytes_received_total",
|
||||
Help: "Total bytes received per tunnel",
|
||||
}, []string{"tunnel_id", "subdomain", "type"})
|
||||
|
||||
TunnelBytesSent = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "drip_tunnel_bytes_sent_total",
|
||||
Help: "Total bytes sent per tunnel",
|
||||
}, []string{"tunnel_id", "subdomain", "type"})
|
||||
|
||||
TunnelActiveConnections = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "drip_tunnel_active_connections",
|
||||
Help: "Current number of active connections per tunnel",
|
||||
}, []string{"tunnel_id", "subdomain", "type"})
|
||||
|
||||
// Rate limiting metrics
|
||||
RateLimitRejections = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "drip_rate_limit_rejections_total",
|
||||
Help: "Total number of rate limit rejections",
|
||||
}, []string{"type", "ip"})
|
||||
|
||||
// System metrics
|
||||
PanicTotal = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "drip_panic_total",
|
||||
Help: "Total number of panics recovered",
|
||||
})
|
||||
|
||||
WorkerPoolSize = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "drip_worker_pool_size",
|
||||
Help: "Current worker pool size",
|
||||
})
|
||||
|
||||
WorkerPoolActiveWorkers = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "drip_worker_pool_active_workers",
|
||||
Help: "Current number of active workers",
|
||||
})
|
||||
|
||||
// HTTP proxy metrics
|
||||
HTTPRequestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "drip_http_request_duration_seconds",
|
||||
Help: "HTTP request duration in seconds",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
}, []string{"method", "status"})
|
||||
|
||||
HTTPRequestsInFlight = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "drip_http_requests_in_flight",
|
||||
Help: "Current number of HTTP requests being processed",
|
||||
})
|
||||
)
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"drip/internal/shared/pool"
|
||||
"drip/internal/shared/protocol"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -33,18 +34,20 @@ var bufioReaderPool = sync.Pool{
|
||||
const openStreamTimeout = 3 * time.Second
|
||||
|
||||
type Handler struct {
|
||||
manager *tunnel.Manager
|
||||
logger *zap.Logger
|
||||
domain string
|
||||
authToken string
|
||||
manager *tunnel.Manager
|
||||
logger *zap.Logger
|
||||
domain string
|
||||
authToken string
|
||||
metricsToken string
|
||||
}
|
||||
|
||||
func NewHandler(manager *tunnel.Manager, logger *zap.Logger, domain string, authToken string) *Handler {
|
||||
func NewHandler(manager *tunnel.Manager, logger *zap.Logger, domain string, authToken string, metricsToken string) *Handler {
|
||||
return &Handler{
|
||||
manager: manager,
|
||||
logger: logger,
|
||||
domain: domain,
|
||||
authToken: authToken,
|
||||
manager: manager,
|
||||
logger: logger,
|
||||
domain: domain,
|
||||
authToken: authToken,
|
||||
metricsToken: metricsToken,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +60,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.serveStats(w, r)
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/metrics" {
|
||||
h.serveMetrics(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
subdomain := h.extractSubdomain(r.Host)
|
||||
if subdomain == "" {
|
||||
@@ -323,30 +330,129 @@ func (h *Handler) extractSubdomain(host string) string {
|
||||
|
||||
func (h *Handler) serveHomePage(w http.ResponseWriter, r *http.Request) {
|
||||
html := `<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Drip - Your Tunnel, Your Domain, Anywhere</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
|
||||
h1 { color: #333; }
|
||||
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }
|
||||
.stats { background: #f9f9f9; padding: 15px; border-radius: 5px; margin: 20px 0; }
|
||||
</style>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Drip - Your Tunnel, Your Domain, Anywhere</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #fff;
|
||||
color: #24292f;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.container { max-width: 720px; margin: 0 auto; padding: 48px 24px; }
|
||||
header { margin-bottom: 48px; }
|
||||
h1 { font-size: 28px; font-weight: 600; margin-bottom: 8px; }
|
||||
h1 span { margin-right: 8px; }
|
||||
.desc { color: #57606a; font-size: 16px; }
|
||||
h2 { font-size: 18px; font-weight: 600; margin: 32px 0 12px; }
|
||||
.code-wrap {
|
||||
position: relative;
|
||||
background: #f6f8fa;
|
||||
border: 1px solid #d0d7de;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.code-wrap pre {
|
||||
margin: 0;
|
||||
padding: 12px 16px;
|
||||
padding-right: 60px;
|
||||
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
||||
font-size: 14px;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
.copy-btn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
background: #fff;
|
||||
border: 1px solid #d0d7de;
|
||||
border-radius: 6px;
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
color: #57606a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.copy-btn:hover { background: #f3f4f6; }
|
||||
.copy-btn svg { width: 16px; height: 16px; }
|
||||
.copy-btn .check { display: none; color: #1a7f37; }
|
||||
.copy-btn.copied .copy { display: none; }
|
||||
.copy-btn.copied .check { display: block; }
|
||||
.links { margin-top: 32px; display: flex; gap: 24px; flex-wrap: wrap; }
|
||||
.links a { color: #0969da; text-decoration: none; font-size: 14px; }
|
||||
.links a:hover { text-decoration: underline; }
|
||||
footer { margin-top: 48px; padding-top: 24px; border-top: 1px solid #d0d7de; }
|
||||
footer a { color: #57606a; text-decoration: none; font-size: 14px; }
|
||||
footer a:hover { color: #0969da; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>💧 Drip - Your Tunnel, Your Domain, Anywhere</h1>
|
||||
<p>A self-hosted tunneling solution to securely expose your services to the internet.</p>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1><span>💧</span>Drip</h1>
|
||||
<p class="desc">Your Tunnel, Your Domain, Anywhere</p>
|
||||
</header>
|
||||
|
||||
<h2>Quick Start</h2>
|
||||
<p>Install the client:</p>
|
||||
<code>bash <(curl -fsSL https://raw.githubusercontent.com/Gouryella/drip/main/scripts/install.sh)</code>
|
||||
<p>A self-hosted tunneling solution to securely expose your services to the internet.</p>
|
||||
|
||||
<p>Start a tunnel:</p>
|
||||
<code>drip http 3000</code><br><br>
|
||||
<code>drip https 443</code><br><br>
|
||||
<code>drip tcp 5432</code>
|
||||
<p><a href="/health">Health Check</a> | <a href="/stats">Statistics</a></p>
|
||||
<h2>Install</h2>
|
||||
<div class="code-wrap">
|
||||
<pre>bash <(curl -fsSL https://raw.githubusercontent.com/Gouryella/drip/main/scripts/install.sh)</pre>
|
||||
<button class="copy-btn" onclick="copy(this)">
|
||||
<svg class="copy" viewBox="0 0 16 16" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg>
|
||||
<svg class="check" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h2>Usage</h2>
|
||||
<div class="code-wrap">
|
||||
<pre>drip http 3000</pre>
|
||||
<button class="copy-btn" onclick="copy(this)">
|
||||
<svg class="copy" viewBox="0 0 16 16" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg>
|
||||
<svg class="check" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="code-wrap">
|
||||
<pre>drip https 443</pre>
|
||||
<button class="copy-btn" onclick="copy(this)">
|
||||
<svg class="copy" viewBox="0 0 16 16" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg>
|
||||
<svg class="check" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="code-wrap">
|
||||
<pre>drip tcp 5432</pre>
|
||||
<button class="copy-btn" onclick="copy(this)">
|
||||
<svg class="copy" viewBox="0 0 16 16" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg>
|
||||
<svg class="check" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="links">
|
||||
<a href="/health">Health Check</a>
|
||||
<a href="/stats">Statistics</a>
|
||||
<a href="/metrics">Prometheus Metrics</a>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<a href="https://github.com/Gouryella/drip" target="_blank">GitHub</a>
|
||||
</footer>
|
||||
</div>
|
||||
<script>
|
||||
function copy(btn) {
|
||||
const text = btn.previousElementSibling.textContent;
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
btn.classList.add('copied');
|
||||
setTimeout(() => { btn.classList.remove('copied'); }, 2000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
@@ -375,7 +481,7 @@ func (h *Handler) serveHealth(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (h *Handler) serveStats(w http.ResponseWriter, r *http.Request) {
|
||||
if h.authToken != "" {
|
||||
if h.metricsToken != "" {
|
||||
// Only accept token via Authorization header (Bearer token)
|
||||
// URL query parameters are insecure (logged, cached, visible in browser history)
|
||||
var token string
|
||||
@@ -384,9 +490,9 @@ func (h *Handler) serveStats(w http.ResponseWriter, r *http.Request) {
|
||||
token = strings.TrimPrefix(authHeader, "Bearer ")
|
||||
}
|
||||
|
||||
if token != h.authToken {
|
||||
if token != h.metricsToken {
|
||||
w.Header().Set("WWW-Authenticate", `Bearer realm="stats"`)
|
||||
http.Error(w, "Unauthorized: provide token via 'Authorization: Bearer <token>' header", http.StatusUnauthorized)
|
||||
http.Error(w, "Unauthorized: provide metrics token via 'Authorization: Bearer <token>' header", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -426,6 +532,26 @@ func (h *Handler) serveStats(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func (h *Handler) serveMetrics(w http.ResponseWriter, r *http.Request) {
|
||||
if h.metricsToken != "" {
|
||||
// Only accept token via Authorization header (Bearer token)
|
||||
var token string
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if strings.HasPrefix(authHeader, "Bearer ") {
|
||||
token = strings.TrimPrefix(authHeader, "Bearer ")
|
||||
}
|
||||
|
||||
if token != h.metricsToken {
|
||||
w.Header().Set("WWW-Authenticate", `Bearer realm="metrics"`)
|
||||
http.Error(w, "Unauthorized: provide metrics token via 'Authorization: Bearer <token>' header", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Serve Prometheus metrics
|
||||
promhttp.Handler().ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
type bufferedReadWriteCloser struct {
|
||||
*bufio.Reader
|
||||
net.Conn
|
||||
|
||||
@@ -372,51 +372,6 @@ func (g *ConnectionGroup) buildSessionHeap(includePrimary bool) *sessionHeap {
|
||||
return h
|
||||
}
|
||||
|
||||
func (g *ConnectionGroup) selectSession() *yamux.Session {
|
||||
h := g.buildSessionHeap(false)
|
||||
if h.Len() == 0 {
|
||||
sessionHeapPool.Put(h)
|
||||
h = g.buildSessionHeap(true)
|
||||
}
|
||||
if h.Len() == 0 {
|
||||
sessionHeapPool.Put(h)
|
||||
return nil
|
||||
}
|
||||
|
||||
entry := heap.Pop(h).(*sessionEntry)
|
||||
session := entry.session
|
||||
|
||||
*h = (*h)[:0]
|
||||
sessionHeapPool.Put(h)
|
||||
|
||||
if session == nil || session.IsClosed() {
|
||||
return nil
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
||||
func (g *ConnectionGroup) sessionsSnapshot(includePrimary bool) []*yamux.Session {
|
||||
g.mu.RLock()
|
||||
defer g.mu.RUnlock()
|
||||
|
||||
if len(g.Sessions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sessions := make([]*yamux.Session, 0, len(g.Sessions))
|
||||
for id, session := range g.Sessions {
|
||||
if session == nil || session.IsClosed() {
|
||||
continue
|
||||
}
|
||||
if id == "primary" && !includePrimary {
|
||||
continue
|
||||
}
|
||||
sessions = append(sessions, session)
|
||||
}
|
||||
|
||||
return sessions
|
||||
}
|
||||
|
||||
func (g *ConnectionGroup) deleteClosedSessions() {
|
||||
g.mu.Lock()
|
||||
for id, session := range g.Sessions {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"drip/internal/server/metrics"
|
||||
"drip/internal/server/tunnel"
|
||||
"drip/internal/shared/pool"
|
||||
"drip/internal/shared/recovery"
|
||||
@@ -56,6 +57,9 @@ func NewListener(address string, tlsConfig *tls.Config, authToken string, manage
|
||||
panicMetrics := recovery.NewPanicMetrics(logger, nil)
|
||||
recoverer := recovery.NewRecoverer(logger, panicMetrics)
|
||||
|
||||
// Initialize worker pool metrics
|
||||
metrics.WorkerPoolSize.Set(float64(workers))
|
||||
|
||||
return &Listener{
|
||||
address: address,
|
||||
tlsConfig: tlsConfig,
|
||||
@@ -237,11 +241,17 @@ func (l *Listener) handleConnection(netConn net.Conn) {
|
||||
l.connections[connID] = conn
|
||||
l.connMu.Unlock()
|
||||
|
||||
// Update connection metrics
|
||||
metrics.TotalConnections.Inc()
|
||||
metrics.ActiveConnections.Inc()
|
||||
|
||||
defer func() {
|
||||
l.connMu.Lock()
|
||||
delete(l.connections, connID)
|
||||
l.connMu.Unlock()
|
||||
|
||||
metrics.ActiveConnections.Dec()
|
||||
|
||||
if !conn.IsHandedOff() {
|
||||
netConn.Close()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"drip/internal/server/metrics"
|
||||
"drip/internal/shared/protocol"
|
||||
"github.com/gorilla/websocket"
|
||||
"go.uber.org/zap"
|
||||
@@ -144,6 +145,8 @@ func (c *Connection) AddBytesIn(n int64) {
|
||||
return
|
||||
}
|
||||
c.bytesIn.Add(n)
|
||||
metrics.BytesReceived.Add(float64(n))
|
||||
metrics.TunnelBytesReceived.WithLabelValues(c.Subdomain, c.Subdomain, c.GetTunnelType().String()).Add(float64(n))
|
||||
}
|
||||
|
||||
func (c *Connection) AddBytesOut(n int64) {
|
||||
@@ -151,6 +154,8 @@ func (c *Connection) AddBytesOut(n int64) {
|
||||
return
|
||||
}
|
||||
c.bytesOut.Add(n)
|
||||
metrics.BytesSent.Add(float64(n))
|
||||
metrics.TunnelBytesSent.WithLabelValues(c.Subdomain, c.Subdomain, c.GetTunnelType().String()).Add(float64(n))
|
||||
}
|
||||
|
||||
func (c *Connection) GetBytesIn() int64 {
|
||||
@@ -163,12 +168,14 @@ func (c *Connection) GetBytesOut() int64 {
|
||||
|
||||
func (c *Connection) IncActiveConnections() {
|
||||
c.activeConnections.Add(1)
|
||||
metrics.TunnelActiveConnections.WithLabelValues(c.Subdomain, c.Subdomain, c.GetTunnelType().String()).Inc()
|
||||
}
|
||||
|
||||
func (c *Connection) DecActiveConnections() {
|
||||
if v := c.activeConnections.Add(-1); v < 0 {
|
||||
c.activeConnections.Store(0)
|
||||
}
|
||||
metrics.TunnelActiveConnections.WithLabelValues(c.Subdomain, c.Subdomain, c.GetTunnelType().String()).Dec()
|
||||
}
|
||||
|
||||
func (c *Connection) GetActiveConnections() int64 {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"drip/internal/server/metrics"
|
||||
"drip/internal/shared/utils"
|
||||
"github.com/gorilla/websocket"
|
||||
"go.uber.org/zap"
|
||||
@@ -176,6 +177,7 @@ func (m *Manager) RegisterWithIP(conn *websocket.Conn, customSubdomain string, r
|
||||
zap.Int64("current", current),
|
||||
zap.Int("max", m.maxTunnels),
|
||||
)
|
||||
metrics.TunnelRegistrationFailures.WithLabelValues("max_tunnels").Inc()
|
||||
return "", ErrTooManyTunnels
|
||||
}
|
||||
if m.tunnelCount.CompareAndSwap(current, current+1) {
|
||||
@@ -199,6 +201,8 @@ func (m *Manager) RegisterWithIP(conn *websocket.Conn, customSubdomain string, r
|
||||
zap.String("ip", remoteIP),
|
||||
zap.Int("limit", m.rateLimit),
|
||||
)
|
||||
metrics.RateLimitRejections.WithLabelValues("registration", remoteIP).Inc()
|
||||
metrics.TunnelRegistrationFailures.WithLabelValues("rate_limit").Inc()
|
||||
return "", ErrRateLimitExceeded
|
||||
}
|
||||
|
||||
@@ -211,11 +215,13 @@ func (m *Manager) RegisterWithIP(conn *websocket.Conn, customSubdomain string, r
|
||||
zap.Int("current", currentPerIP),
|
||||
zap.Int("max", m.maxTunnelsPerIP),
|
||||
)
|
||||
metrics.TunnelRegistrationFailures.WithLabelValues("max_per_ip").Inc()
|
||||
return "", ErrTooManyPerIP
|
||||
}
|
||||
|
||||
// Reserve per-IP slot while still holding the lock
|
||||
m.tunnelsByIP[remoteIP]++
|
||||
metrics.TunnelsByIP.WithLabelValues(remoteIP).Set(float64(m.tunnelsByIP[remoteIP]))
|
||||
m.ipMu.Unlock()
|
||||
}
|
||||
|
||||
@@ -227,6 +233,9 @@ func (m *Manager) RegisterWithIP(conn *websocket.Conn, customSubdomain string, r
|
||||
m.tunnelsByIP[remoteIP]--
|
||||
if m.tunnelsByIP[remoteIP] == 0 {
|
||||
delete(m.tunnelsByIP, remoteIP)
|
||||
metrics.TunnelsByIP.DeleteLabelValues(remoteIP)
|
||||
} else {
|
||||
metrics.TunnelsByIP.WithLabelValues(remoteIP).Set(float64(m.tunnelsByIP[remoteIP]))
|
||||
}
|
||||
}
|
||||
m.ipMu.Unlock()
|
||||
@@ -293,6 +302,10 @@ func (m *Manager) RegisterWithIP(conn *websocket.Conn, customSubdomain string, r
|
||||
zap.Int64("total_tunnels", m.tunnelCount.Load()),
|
||||
)
|
||||
|
||||
// Update Prometheus metrics
|
||||
metrics.TunnelRegistrations.Inc()
|
||||
metrics.TunnelCount.Set(float64(m.tunnelCount.Load()))
|
||||
|
||||
return subdomain, nil
|
||||
}
|
||||
|
||||
@@ -321,6 +334,9 @@ func (m *Manager) Unregister(subdomain string) {
|
||||
m.tunnelsByIP[remoteIP]--
|
||||
if m.tunnelsByIP[remoteIP] == 0 {
|
||||
delete(m.tunnelsByIP, remoteIP)
|
||||
metrics.TunnelsByIP.DeleteLabelValues(remoteIP)
|
||||
} else {
|
||||
metrics.TunnelsByIP.WithLabelValues(remoteIP).Set(float64(m.tunnelsByIP[remoteIP]))
|
||||
}
|
||||
}
|
||||
m.ipMu.Unlock()
|
||||
@@ -330,6 +346,9 @@ func (m *Manager) Unregister(subdomain string) {
|
||||
zap.String("subdomain", subdomain),
|
||||
zap.Int64("total_tunnels", m.tunnelCount.Load()),
|
||||
)
|
||||
|
||||
// Update Prometheus metrics
|
||||
metrics.TunnelCount.Set(float64(m.tunnelCount.Load()))
|
||||
}
|
||||
|
||||
// Get retrieves a tunnel connection by subdomain
|
||||
@@ -394,6 +413,9 @@ func (m *Manager) CleanupStale(timeout time.Duration) int {
|
||||
m.tunnelsByIP[remoteIP]--
|
||||
if m.tunnelsByIP[remoteIP] == 0 {
|
||||
delete(m.tunnelsByIP, remoteIP)
|
||||
metrics.TunnelsByIP.DeleteLabelValues(remoteIP)
|
||||
} else {
|
||||
metrics.TunnelsByIP.WithLabelValues(remoteIP).Set(float64(m.tunnelsByIP[remoteIP]))
|
||||
}
|
||||
}
|
||||
m.ipMu.Unlock()
|
||||
|
||||
@@ -79,20 +79,6 @@ func (p *BufferPool) Put(buf *[]byte) {
|
||||
// Note: buffers with non-standard sizes are not pooled (let GC handle them)
|
||||
}
|
||||
|
||||
// GetXLarge returns a 1MB buffer for bulk data transfers
|
||||
func (p *BufferPool) GetXLarge() *[]byte {
|
||||
return p.xlarge.Get().(*[]byte)
|
||||
}
|
||||
|
||||
// PutXLarge returns a 1MB buffer to the pool
|
||||
func (p *BufferPool) PutXLarge(buf *[]byte) {
|
||||
if buf == nil || cap(*buf) != SizeXLarge {
|
||||
return
|
||||
}
|
||||
*buf = (*buf)[:cap(*buf)]
|
||||
p.xlarge.Put(buf)
|
||||
}
|
||||
|
||||
var globalBufferPool = NewBufferPool()
|
||||
|
||||
func GetBuffer(size int) *[]byte {
|
||||
@@ -102,13 +88,3 @@ func GetBuffer(size int) *[]byte {
|
||||
func PutBuffer(buf *[]byte) {
|
||||
globalBufferPool.Put(buf)
|
||||
}
|
||||
|
||||
// GetXLargeBuffer returns a 1MB buffer from the global pool
|
||||
func GetXLargeBuffer() *[]byte {
|
||||
return globalBufferPool.GetXLarge()
|
||||
}
|
||||
|
||||
// PutXLargeBuffer returns a 1MB buffer to the global pool
|
||||
func PutXLargeBuffer(buf *[]byte) {
|
||||
globalBufferPool.PutXLarge(buf)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"drip/internal/server/metrics"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -39,6 +40,7 @@ func NewPanicMetrics(logger *zap.Logger, alerter Alerter) *PanicMetrics {
|
||||
|
||||
func (pm *PanicMetrics) RecordPanic(location string, panicValue interface{}) {
|
||||
atomic.AddUint64(&pm.totalPanics, 1)
|
||||
metrics.PanicTotal.Inc()
|
||||
|
||||
pm.mu.Lock()
|
||||
|
||||
|
||||
@@ -59,8 +59,9 @@ MSG_EN=(
|
||||
["enter_domain"]="Enter your domain (e.g., tunnel.example.com)"
|
||||
["domain_required"]="Domain is required"
|
||||
["enter_port"]="Enter server port"
|
||||
["enter_token"]="Enter authentication token (leave empty to generate)"
|
||||
["token_generated"]="Token generated"
|
||||
["enter_token"]="Enter authentication token (leave empty to auto-generate)"
|
||||
["token_generated"]="Authentication token generated"
|
||||
["metrics_token_generated"]="Metrics token generated"
|
||||
["enter_cert_path"]="Enter TLS certificate path (public key)"
|
||||
["enter_key_path"]="Enter TLS private key path"
|
||||
["cert_not_found"]="Certificate file not found"
|
||||
@@ -94,7 +95,8 @@ MSG_EN=(
|
||||
["install_complete"]="Installation completed!"
|
||||
["client_info"]="Client connection info"
|
||||
["server_addr"]="Server"
|
||||
["token_label"]="Token"
|
||||
["token_label"]="Auth Token"
|
||||
["metrics_token_label"]="Metrics Token"
|
||||
["service_commands"]="Service management commands"
|
||||
["cmd_start"]="Start service"
|
||||
["cmd_stop"]="Stop service"
|
||||
@@ -149,7 +151,8 @@ MSG_ZH=(
|
||||
["domain_required"]="域名是必填项"
|
||||
["enter_port"]="输入服务器端口"
|
||||
["enter_token"]="输入认证令牌(留空自动生成)"
|
||||
["token_generated"]="令牌已生成"
|
||||
["token_generated"]="认证令牌已生成"
|
||||
["metrics_token_generated"]="监控令牌已生成"
|
||||
["enter_cert_path"]="输入 TLS 证书路径(公钥)"
|
||||
["enter_key_path"]="输入 TLS 私钥路径"
|
||||
["cert_not_found"]="证书文件未找到"
|
||||
@@ -183,7 +186,8 @@ MSG_ZH=(
|
||||
["install_complete"]="安装完成!"
|
||||
["client_info"]="客户端连接信息"
|
||||
["server_addr"]="服务器"
|
||||
["token_label"]="令牌"
|
||||
["token_label"]="认证令牌"
|
||||
["metrics_token_label"]="监控令牌"
|
||||
["service_commands"]="服务管理命令"
|
||||
["cmd_start"]="启动服务"
|
||||
["cmd_stop"]="停止服务"
|
||||
@@ -546,7 +550,7 @@ install_binary() {
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
generate_token() {
|
||||
# Generate a random 32-character token
|
||||
# Generate a random 32-character token (16 bytes = 32 hex chars)
|
||||
if command -v openssl &> /dev/null; then
|
||||
openssl rand -hex 16
|
||||
else
|
||||
@@ -583,7 +587,7 @@ configure_server() {
|
||||
read -p "$(msg enter_tcp_max) [$DEFAULT_TCP_PORT_MAX]: " TCP_PORT_MAX < /dev/tty
|
||||
TCP_PORT_MAX="${TCP_PORT_MAX:-$DEFAULT_TCP_PORT_MAX}"
|
||||
|
||||
# Token
|
||||
# Authentication token (user can provide or auto-generate)
|
||||
echo ""
|
||||
read -p "$(msg enter_token): " TOKEN < /dev/tty
|
||||
if [[ -z "$TOKEN" ]]; then
|
||||
@@ -591,6 +595,10 @@ configure_server() {
|
||||
print_success "$(msg token_generated): $TOKEN"
|
||||
fi
|
||||
|
||||
# Metrics token (always auto-generated)
|
||||
METRICS_TOKEN=$(generate_token)
|
||||
print_success "$(msg metrics_token_generated): $METRICS_TOKEN"
|
||||
|
||||
# TLS certificate selection
|
||||
print_panel "$(msg cert_option_title)" \
|
||||
"${GREEN}1)${NC} $(msg cert_option_certbot)" \
|
||||
@@ -946,6 +954,7 @@ DRIP_PORT=${PORT}
|
||||
DRIP_PUBLIC_PORT=${PUBLIC_PORT}
|
||||
DRIP_DOMAIN=${DOMAIN}
|
||||
DRIP_TOKEN=${TOKEN}
|
||||
DRIP_METRICS_TOKEN=${METRICS_TOKEN}
|
||||
|
||||
# TLS certificate paths
|
||||
DRIP_TLS_CERT=${CERT_PATH}
|
||||
@@ -995,8 +1004,9 @@ show_completion() {
|
||||
print_panel "$(msg install_complete)"
|
||||
|
||||
echo -e "${CYAN}$(msg client_info):${NC}"
|
||||
echo -e " ${BOLD}$(msg server_addr):${NC} ${DOMAIN}:${PORT}"
|
||||
echo -e " ${BOLD}$(msg token_label):${NC} ${TOKEN}"
|
||||
echo -e " ${BOLD}$(msg server_addr):${NC} ${DOMAIN}:${PORT}"
|
||||
echo -e " ${BOLD}$(msg token_label):${NC} ${TOKEN}"
|
||||
echo -e " ${BOLD}$(msg metrics_token_label):${NC} ${METRICS_TOKEN}"
|
||||
echo ""
|
||||
|
||||
echo -e "${CYAN}$(msg service_commands):${NC}"
|
||||
|
||||
Reference in New Issue
Block a user