Skip to content

Commit 3f0a311

Browse files
authored
feat: add connection and request limiting configuration options (#153)
* feat: add connection and request limiting configuration options - Add -max-requests-per-tunnel flag to limit queued requests per tunnel (default: 20) - Add -max-clients-per-token flag to limit concurrent clients per token (default: unlimited) - Implement client count tracking with automatic cleanup on disconnect - Update /_stats endpoint to display configuration limits and client counts - Add comprehensive tests for both limiting features - Update documentation with usage examples and monitoring information These features address issues #146 and #137, providing better resource control and preventing unauthorized token sharing or connection conflicts. Closes #146 Closes #137 * feat: add connection and request limiting configuration options - Add -max-requests-per-tunnel flag to limit queued requests per tunnel (default: 20) - Add -max-clients-per-token flag to limit concurrent clients per token (default: unlimited) - Implement client count tracking with automatic cleanup on disconnect - Update /_stats endpoint to display configuration limits and client counts - Add comprehensive tests for both limiting features - Update documentation with usage examples and monitoring information These features address issues #146 and #137, providing better resource control and preventing unauthorized token sharing or connection conflicts. Closes #146 Closes #137
1 parent faa82b3 commit 3f0a311

23 files changed

+4151
-661
lines changed

.github/workflows/commitlint.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ jobs:
1010
lint:
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
13+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
1414
- uses: wagoid/commitlint-github-action@b948419dd99f3fd78a6548d48f94e3df7f6bf3ed
1515
with:
1616
configFile: .commitlintrc.yml
17-
failOnWarnings: true
17+
failOnWarnings: true

.github/workflows/go.yml

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ name: Go
55

66
on:
77
push:
8-
branches: [ "master" ]
8+
branches: ["master"]
99
pull_request:
10-
branches: [ "master" ]
10+
branches: ["master"]
1111
workflow_call:
1212

1313
# Set default permissions
@@ -19,47 +19,46 @@ jobs:
1919
runs-on: ubuntu-latest
2020
permissions:
2121
contents: read
22-
pull-requests: read # For golangci-lint annotations
22+
pull-requests: read # For golangci-lint annotations
2323
steps:
24-
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
25-
- name: Set up Go
26-
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
27-
with:
28-
go-version: '1.24'
29-
cache: true
30-
cache-dependency-path: go.sum
31-
32-
- name: golangci-lint
33-
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
34-
with:
35-
version: v2.1.2
36-
37-
- name: Build
38-
run: go build -v ./...
24+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
25+
- name: Set up Go
26+
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
27+
with:
28+
go-version: '1.24'
29+
cache: true
30+
cache-dependency-path: go.sum
31+
32+
- name: golangci-lint
33+
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
34+
with:
35+
version: v2.1.2
36+
37+
- name: Build
38+
run: go build -v ./...
3939

4040
test:
4141
needs: build
4242
runs-on: ubuntu-latest
4343
permissions:
4444
contents: read
4545
steps:
46-
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
47-
48-
- name: Set up Go
49-
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
50-
with:
51-
go-version: '1.24'
52-
cache: true
53-
cache-dependency-path: go.sum
54-
55-
- name: Run tests
56-
run: go test -v ./...
57-
58-
- name: Generate coverage report
59-
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
60-
61-
- name: Upload coverage to Codecov
62-
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
63-
with:
64-
token: ${{ secrets.CODECOV_TOKEN }}
46+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
47+
48+
- name: Set up Go
49+
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
50+
with:
51+
go-version: '1.24'
52+
cache: true
53+
cache-dependency-path: go.sum
54+
55+
- name: Run tests
56+
run: go test -v ./...
57+
58+
- name: Generate coverage report
59+
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
6560

61+
- name: Upload coverage to Codecov
62+
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
63+
with:
64+
token: ${{ secrets.CODECOV_TOKEN }}

.github/workflows/release.yml

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,20 @@ jobs:
2222
contents: write # Required for creating releases
2323
id-token: write # Required for signing
2424
packages: write # Required for container publishing
25-
2625
steps:
27-
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
26+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
2827
with:
2928
fetch-depth: 0
30-
31-
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
29+
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
3230
with:
3331
go-version: '1.24'
3432
cache: true
3533
cache-dependency-path: go.sum
36-
3734
- name: Run GoReleaser
38-
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
35+
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0
3936
with:
4037
distribution: goreleaser
4138
version: latest
4239
args: release --clean
4340
env:
44-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.yamllint

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
extends: default
3+
4+
rules:
5+
document-start: disable # GitHub Actions workflows don't need document start
6+
line-length:
7+
max: 120 # Increased from 80 to accommodate GitHub Actions long names
8+
truthy:
9+
allowed-values: ['true', 'false', 'on'] # Allow 'on' for GitHub Actions
10+
comments:
11+
min-spaces-from-content: 1 # Allow single space before comments

CLAUDE.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ WStunnel is a WebSocket-based reverse HTTP/HTTPS tunneling solution that enables
2020
- `go test ./...` - Run all tests in the project
2121

2222
## Lint Commands
23-
- `make lint` - Run gofmt check and go vet
23+
- `make lint` - Run gofmt check, go vet, golangci-lint, and yamllint
2424
- `golangci-lint run` - Run comprehensive linting checks
25+
- `yamllint .github/workflows/` - Check YAML formatting in GitHub workflows
2526

2627
## Code Style Guidelines
2728
- **Formatting**: Use gofmt, tabs for indentation
@@ -53,6 +54,8 @@ WStunnel is a WebSocket-based reverse HTTP/HTTPS tunneling solution that enables
5354
- Port allocation uses `:0` to get random available ports
5455
- Use standard Go testing package with table-driven tests
5556
- Test files should be named `*_test.go` and placed alongside the code they test
57+
- **IMPORTANT**: Do NOT use Ginkgo/Gomega testing frameworks - use standard Go testing only
58+
- **NEVER** create or convert tests to use Ginkgo - always use the standard testing package
5659

5760
## Security Considerations
5861
- Tokens must be at least 16 characters
@@ -72,11 +75,26 @@ WStunnel is a WebSocket-based reverse HTTP/HTTPS tunneling solution that enables
7275
- WebSocket ping/pong failures often indicate network issues or proxy interference
7376
- Request timeouts can be tuned with `-timeout` flag (default 30s)
7477

78+
## Configuration Options
79+
- **Max Requests Per Tunnel**: Use `-max-requests-per-tunnel N` to limit queued requests per tunnel (default: 20)
80+
- **Max Clients Per Token**: Use `-max-clients-per-token N` to limit concurrent clients per token (default: 0/unlimited)
81+
- When a tunnel reaches the max request limit, new requests return "too many requests in-flight, tunnel broken?"
82+
- When a token reaches the max client limit, new connections return HTTP 429 "Maximum number of clients reached"
83+
- Client counts are automatically decremented when clients disconnect
84+
7585
## CodeRabbit Review Settings
7686
The project uses CodeRabbit for automated code reviews (see `.coderabbit.yaml`). When writing code, ensure compliance with:
7787
- **Go conventions**: Use gofmt, organize imports (stdlib first), proper error handling
7888
- **Security**: Never log passwords/tokens, validate certificates, prevent timing attacks
7989
- **Testing**: Use standard Go testing with table-driven tests, cover edge cases
8090
- **Path-specific rules**: WebSocket code must follow patterns in tunnel/ws.go, use goroutine-per-request
8191
- **Excluded paths**: vendor/, build/, node_modules/, generated code, coverage.txt are not reviewed
82-
- CodeRabbit auto-approves dependency updates from Renovate and documentation-only changes
92+
- CodeRabbit auto-approves dependency updates from Renovate and documentation-only changes
93+
94+
## CodeRabbit Fix Tool
95+
Use `~/bin/coderabbit-fix` to automatically apply CodeRabbit suggestions:
96+
- `coderabbit-fix 153 --ai-format` - Generate AI-formatted prompts from PR 153
97+
- `coderabbit-fix 153` - Apply all fixes from PR 153
98+
- `coderabbit-fix 153 --dry-run` - Show what would be changed without applying
99+
- The tool extracts detailed instructions from CodeRabbit comments including "Prompt for AI Agents" sections
100+
- Always run `make lint` and `make test` after applying fixes to ensure code quality

Makefile

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ endif
4646

4747
# the default target builds a binary in the top-level dir for whatever the local OS is
4848
default: $(EXE)
49-
$(EXE): *.go version
49+
$(EXE): *.go
5050
go build -ldflags "-X 'main.VV=$(NAME)_$(TRAVIS_BRANCH)_$(DATE)_$(TRAVIS_COMMIT)'" -o $(EXE) .
5151

5252
# the standard build produces a "local" executable, a linux tgz, and a darwin (macos) tgz
@@ -55,7 +55,7 @@ build: depend $(EXE) build/$(NAME)-linux-amd64.tgz build/$(NAME)-windows-amd64.z
5555

5656
# create a tgz with the binary and any artifacts that are necessary
5757
# note the hack to allow for various GOOS & GOARCH combos, sigh
58-
build/$(NAME)-%.tgz: *.go version depend
58+
build/$(NAME)-%.tgz: *.go depend
5959
rm -rf build/$(NAME)
6060
mkdir -p build/$(NAME)
6161
tgt=$*; GOOS=$${tgt%-*} GOARCH=$${tgt#*-} go build -ldflags "-X 'main.VV=$(NAME)_$(TRAVIS_BRANCH)_$(DATE)_$(TRAVIS_COMMIT)'" -o build/$(NAME)/$(NAME) .
@@ -68,7 +68,7 @@ build/$(NAME)-%.tgz: *.go version depend
6868
tar -zcf $@ -C build ./$(NAME)
6969
rm -r build/$(NAME)
7070

71-
build/$(NAME)-%.zip: *.go version depend
71+
build/$(NAME)-%.zip: *.go depend
7272
mkdir -p build/$(NAME)
7373
tgt=$*; GOOS=$${tgt%-*} GOARCH=$${tgt#*-} go build -ldflags "-X 'main.VV=$(NAME)_$(TRAVIS_BRANCH)_$(DATE)_$(TRAVIS_COMMIT)'" -o build/$(NAME)/$(NAME).exe .
7474
zip $@ build/$(NAME)/$(NAME).exe
@@ -101,12 +101,9 @@ upload: depend
101101
fi; \
102102
done)
103103

104-
# produce a version string that is embedded into the binary that captures the branch, the date
105-
# and the commit we're building
104+
# version target is now a no-op since we use ldflags to set VV
106105
version:
107-
@echo "package main; const VV = \"$(NAME) $(TRAVIS_BRANCH) - $(DATE) - $(TRAVIS_COMMIT)\"" \
108-
>version.go
109-
@echo "version.go: `cat version.go`"
106+
@echo "Version is set via ldflags: $(NAME) $(TRAVIS_BRANCH) - $(DATE) - $(TRAVIS_COMMIT)"
110107

111108
# Installing build dependencies is a bit of a mess. Don't want to spend lots of time in
112109
# Travis doing this. The folllowing just relies on go get no reinstalling when it's already
@@ -115,7 +112,8 @@ depend:
115112
go mod download
116113

117114
clean:
118-
@echo "package main; const VV = \"$(NAME) unversioned - $(DATE)\"" >version.go
115+
rm -f $(EXE) version.go
116+
rm -rf build/
119117

120118
# gofmt uses the awkward *.go */*.go because gofmt -l . descends into the Godeps workspace
121119
# and then pointlessly complains about bad formatting in imported packages, sigh
@@ -130,6 +128,28 @@ lint:
130128
else \
131129
echo "golangci-lint not found, skipping"; \
132130
fi
131+
@if command -v yamllint > /dev/null; then \
132+
echo "Running yamllint..." && \
133+
yamllint --config-file .yamllint .github/workflows/ && \
134+
echo "✓ YAML files passed linting"; \
135+
else \
136+
echo "yamllint not found, skipping. Install with: pip install yamllint"; \
137+
fi
138+
139+
# Auto-fix YAML files
140+
yamllint-fix:
141+
@if command -v yamllint > /dev/null; then \
142+
echo "Checking YAML files for issues..." && \
143+
if ! yamllint .github/workflows/ > /dev/null 2>&1; then \
144+
echo "YAML linting issues detected. Note: yamllint doesn't have auto-fix capability." && \
145+
echo "Showing issues that need manual fixing:" && \
146+
yamllint .github/workflows/; \
147+
else \
148+
echo "✓ All YAML files are valid"; \
149+
fi \
150+
else \
151+
echo "yamllint not found. Install with: pip install yamllint"; \
152+
fi
133153

134154
travis-test: lint
135155
go test -race -coverprofile=coverage.txt -covermode=atomic ./...

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,46 @@ WSTUNSRV RUNNING
103103
$
104104
```
105105

106+
#### Server Configuration Options
107+
108+
The WStunnel server supports several configuration options to control resource usage and security:
109+
110+
**Password Authentication:**
106111
To require passwords for specific tokens, use the `-passwords` option:
107112

108113
```bash
109114
$ ./wstunnel srv -port 8080 -passwords 'token1:password1,token2:password2' &
115+
2024/01/19 09:51:31 Listening on port 8080
116+
$ # Server is now running with password authentication
117+
```
118+
119+
**Request Limiting:**
120+
To control the maximum number of queued requests per tunnel (default: 20):
121+
122+
```bash
123+
$ ./wstunnel srv -port 8080 -max-requests-per-tunnel 50 &
124+
2024/01/19 09:51:31 Listening on port 8080
125+
```
126+
127+
This prevents any single tunnel from consuming too many server resources by limiting how many requests can be queued for processing.
128+
129+
**Client Limiting:**
130+
To limit the number of clients that can connect with the same token (default: unlimited):
131+
132+
```bash
133+
$ ./wstunnel srv -port 8080 -max-clients-per-token 1 &
134+
2024/01/19 09:51:31 Listening on port 8080
135+
```
136+
137+
This is useful when you want to ensure only a single client instance per token is allowed, preventing unauthorized token sharing or connection conflicts.
138+
139+
**Combined Configuration Example:**
140+
141+
```bash
142+
$ ./wstunnel srv -port 8080 \
143+
-passwords 'prod-token:secure-password,dev-token:dev-pass' \
144+
-max-requests-per-tunnel 30 \
145+
-max-clients-per-token 2 &
110146
```
111147

112148
### Start tunnel
@@ -237,16 +273,23 @@ server {
237273
WStunnel server provides a `/_stats` endpoint that displays information about connected tunnels. When accessed from localhost, it provides detailed information including:
238274

239275
- Number of active tunnels
276+
- Server configuration limits
240277
- Token information for each tunnel
241278
- Pending requests per tunnel
242279
- Client IP address and reverse DNS lookup
243280
- Client version information
244281
- Idle time for each tunnel
282+
- Current client counts per token (when limits are configured)
245283

246284
Example output:
247285

248286
```text
249287
tunnels=2
288+
max_requests_per_tunnel=20
289+
max_clients_per_token=1
290+
token_clients_my_token=1
291+
token_clients_another_=1
292+
total_clients=2
250293

251294
tunnel00_token=my_token_...
252295
tunnel00_req_pending=0
@@ -262,6 +305,13 @@ tunnel01_client_version=wstunnel v1.0.0
262305
tunnel01_idle_secs=120.5
263306
```
264307

308+
The configuration limits section shows:
309+
310+
- `max_requests_per_tunnel`: Maximum queued requests per tunnel
311+
- `max_clients_per_token`: Maximum clients allowed per token (0 = unlimited)
312+
- `token_clients_*`: Current number of clients for each token (when limits are configured)
313+
- `total_clients`: Total number of connected clients across all tokens
314+
265315
Note: Full statistics are only available when the endpoint is accessed from localhost. Remote requests will only see the total number of tunnels.
266316

267317
### Reading wstunnel server logs

go.mod

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,10 @@ require (
1111
)
1212

1313
require (
14-
github.com/onsi/ginkgo/v2 v2.23.4
15-
github.com/onsi/gomega v1.37.0
16-
)
17-
18-
require (
19-
github.com/go-logr/logr v1.4.3 // indirect
20-
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
21-
github.com/google/go-cmp v0.7.0 // indirect
22-
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect
2314
github.com/mattn/go-colorable v0.1.14 // indirect
2415
github.com/mattn/go-isatty v0.0.20 // indirect
25-
go.uber.org/automaxprocs v1.6.0 // indirect
26-
golang.org/x/net v0.40.0 // indirect
2716
golang.org/x/sys v0.33.0 // indirect
2817
golang.org/x/term v0.32.0 // indirect
29-
golang.org/x/text v0.25.0 // indirect
30-
golang.org/x/tools v0.33.0 // indirect
31-
google.golang.org/protobuf v1.36.6 // indirect
32-
gopkg.in/yaml.v3 v3.0.1 // indirect
3318
)
3419

3520
replace github.com/imdario/mergo => dario.cat/mergo v1.0.2

0 commit comments

Comments
 (0)