mirror of
https://github.com/Gouryella/drip.git
synced 2026-02-26 22:31:35 +00:00
feat(client): Add bandwidth limit function support
- Implement client bandwidth limitation parameter --bandwidth, supporting 1M, 1MB, 1G and other formats - Added parseBandwidth function to parse bandwidth values and verify them - Added bandwidth limit option in HTTP, HTTPS, TCP commands - Pass bandwidth configuration to the server through protocol - Add relevant test cases to verify the bandwidth analysis function feat(server): implements server-side bandwidth limitation function - Add bandwidth limitation logic in connection processing, using token bucket algorithm - Implement an effective rate limiting strategy that minimizes the bandwidth of the client and server - Added QoS limiter and restricted connection wrapper - Integrated bandwidth throttling in HTTP and WebSocket proxies - Added global bandwidth limit and burst multiplier settings in server configuration docs: Updated documentation to describe bandwidth limiting functionality - Add 2025-02-14 version update instructions in README and README_CN - Add bandwidth limit function description and usage examples - Provide client and server configuration examples and parameter descriptions
This commit is contained in:
@@ -21,6 +21,7 @@ var (
|
||||
authPass string
|
||||
authBearer string
|
||||
transport string
|
||||
bandwidth string
|
||||
)
|
||||
|
||||
var httpCmd = &cobra.Command{
|
||||
@@ -37,6 +38,7 @@ Example:
|
||||
drip http 3000 --auth secret Enable proxy authentication with password
|
||||
drip http 3000 --auth-bearer sk-xxx Enable proxy authentication with bearer token
|
||||
drip http 3000 --transport wss Use WebSocket over TLS (CDN-friendly)
|
||||
drip http 3000 --bandwidth 1M Limit bandwidth to 1 MB/s
|
||||
|
||||
Configuration:
|
||||
First time: Run 'drip config init' to save server and token
|
||||
@@ -45,7 +47,13 @@ Configuration:
|
||||
Transport options:
|
||||
auto - Automatically select based on server address (default)
|
||||
tcp - Direct TLS 1.3 connection
|
||||
wss - WebSocket over TLS (works through CDN like Cloudflare)`,
|
||||
wss - WebSocket over TLS (works through CDN like Cloudflare)
|
||||
|
||||
Bandwidth format:
|
||||
1K, 1KB - 1 kilobyte per second (1024 bytes/s)
|
||||
1M, 1MB - 1 megabyte per second (1048576 bytes/s)
|
||||
1G, 1GB - 1 gigabyte per second
|
||||
1024 - 1024 bytes per second (raw number)`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runHTTP,
|
||||
SilenceUsage: true,
|
||||
@@ -61,6 +69,7 @@ func init() {
|
||||
httpCmd.Flags().StringVar(&authPass, "auth", "", "Password for proxy authentication")
|
||||
httpCmd.Flags().StringVar(&authBearer, "auth-bearer", "", "Bearer token for proxy authentication")
|
||||
httpCmd.Flags().StringVar(&transport, "transport", "auto", "Transport protocol: auto, tcp, wss (WebSocket over TLS)")
|
||||
httpCmd.Flags().StringVar(&bandwidth, "bandwidth", "", "Bandwidth limit (e.g., 1M, 500K, 1G)")
|
||||
httpCmd.Flags().BoolVar(&daemonMarker, "daemon-child", false, "Internal flag for daemon child process")
|
||||
httpCmd.Flags().MarkHidden("daemon-child")
|
||||
rootCmd.AddCommand(httpCmd)
|
||||
@@ -85,6 +94,11 @@ func runHTTP(_ *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
bw, err := parseBandwidth(bandwidth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
connConfig := &tcp.ConnectorConfig{
|
||||
ServerAddr: serverAddr,
|
||||
Token: token,
|
||||
@@ -98,6 +112,7 @@ func runHTTP(_ *cobra.Command, args []string) error {
|
||||
AuthPass: authPass,
|
||||
AuthBearer: authBearer,
|
||||
Transport: parseTransport(transport),
|
||||
Bandwidth: bw,
|
||||
}
|
||||
|
||||
var daemon *DaemonInfo
|
||||
@@ -118,3 +133,41 @@ func parseTransport(s string) tcp.TransportType {
|
||||
return tcp.TransportAuto
|
||||
}
|
||||
}
|
||||
|
||||
func parseBandwidth(s string) (int64, error) {
|
||||
if s == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
s = strings.TrimSpace(strings.ToUpper(s))
|
||||
if s == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var multiplier int64 = 1
|
||||
switch {
|
||||
case strings.HasSuffix(s, "GB") || strings.HasSuffix(s, "G"):
|
||||
multiplier = 1024 * 1024 * 1024
|
||||
s = strings.TrimSuffix(strings.TrimSuffix(s, "GB"), "G")
|
||||
case strings.HasSuffix(s, "MB") || strings.HasSuffix(s, "M"):
|
||||
multiplier = 1024 * 1024
|
||||
s = strings.TrimSuffix(strings.TrimSuffix(s, "MB"), "M")
|
||||
case strings.HasSuffix(s, "KB") || strings.HasSuffix(s, "K"):
|
||||
multiplier = 1024
|
||||
s = strings.TrimSuffix(strings.TrimSuffix(s, "KB"), "K")
|
||||
case strings.HasSuffix(s, "B"):
|
||||
s = strings.TrimSuffix(s, "B")
|
||||
}
|
||||
|
||||
val, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil || val < 0 {
|
||||
return 0, fmt.Errorf("invalid bandwidth value: %q (use format like 1M, 500K, 1G)", s)
|
||||
}
|
||||
|
||||
result := val * multiplier
|
||||
if val > 0 && result/multiplier != val {
|
||||
return 0, fmt.Errorf("bandwidth value overflow: %q", s)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
59
internal/client/cli/http_test.go
Normal file
59
internal/client/cli/http_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseBandwidth(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want int64
|
||||
wantErr bool
|
||||
}{
|
||||
{"", 0, false},
|
||||
{"0", 0, false},
|
||||
{"1024", 1024, false},
|
||||
{"1K", 1024, false},
|
||||
{"1KB", 1024, false},
|
||||
{"1k", 1024, false},
|
||||
{"1M", 1024 * 1024, false},
|
||||
{"1MB", 1024 * 1024, false},
|
||||
{"1m", 1024 * 1024, false},
|
||||
{"10M", 10 * 1024 * 1024, false},
|
||||
{"1G", 1024 * 1024 * 1024, false},
|
||||
{"1GB", 1024 * 1024 * 1024, false},
|
||||
{"500K", 500 * 1024, false},
|
||||
{"100M", 100 * 1024 * 1024, false},
|
||||
{" 1M ", 1024 * 1024, false},
|
||||
{"1B", 1, false},
|
||||
{"100B", 100, false},
|
||||
{"invalid", 0, true},
|
||||
{"abc", 0, true},
|
||||
{"-1M", 0, true},
|
||||
{"-100", 0, true},
|
||||
{"1.5M", 0, true},
|
||||
{"M", 0, true},
|
||||
{"K", 0, true},
|
||||
{"9223372036854775807K", 0, true},
|
||||
{"9999999999999999999G", 0, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
got, err := parseBandwidth(tt.input)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("parseBandwidth(%q) = %d, want error", tt.input, got)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("parseBandwidth(%q) unexpected error: %v", tt.input, err)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("parseBandwidth(%q) = %d, want %d", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ Example:
|
||||
drip https 443 --auth secret Enable proxy authentication with password
|
||||
drip https 443 --auth-bearer sk-xxx Enable proxy authentication with bearer token
|
||||
drip https 443 --transport wss Use WebSocket over TLS (CDN-friendly)
|
||||
drip https 443 --bandwidth 1M Limit bandwidth to 1 MB/s
|
||||
|
||||
Configuration:
|
||||
First time: Run 'drip config init' to save server and token
|
||||
@@ -32,7 +33,13 @@ Configuration:
|
||||
Transport options:
|
||||
auto - Automatically select based on server address (default)
|
||||
tcp - Direct TLS 1.3 connection
|
||||
wss - WebSocket over TLS (works through CDN like Cloudflare)`,
|
||||
wss - WebSocket over TLS (works through CDN like Cloudflare)
|
||||
|
||||
Bandwidth format:
|
||||
1K, 1KB - 1 kilobyte per second (1024 bytes/s)
|
||||
1M, 1MB - 1 megabyte per second (1048576 bytes/s)
|
||||
1G, 1GB - 1 gigabyte per second
|
||||
1024 - 1024 bytes per second (raw number)`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runHTTPS,
|
||||
SilenceUsage: true,
|
||||
@@ -48,6 +55,7 @@ func init() {
|
||||
httpsCmd.Flags().StringVar(&authPass, "auth", "", "Password for proxy authentication")
|
||||
httpsCmd.Flags().StringVar(&authBearer, "auth-bearer", "", "Bearer token for proxy authentication")
|
||||
httpsCmd.Flags().StringVar(&transport, "transport", "auto", "Transport protocol: auto, tcp, wss (WebSocket over TLS)")
|
||||
httpsCmd.Flags().StringVar(&bandwidth, "bandwidth", "", "Bandwidth limit (e.g., 1M, 500K, 1G)")
|
||||
httpsCmd.Flags().BoolVar(&daemonMarker, "daemon-child", false, "Internal flag for daemon child process")
|
||||
httpsCmd.Flags().MarkHidden("daemon-child")
|
||||
rootCmd.AddCommand(httpsCmd)
|
||||
@@ -72,6 +80,11 @@ func runHTTPS(_ *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
bw, err := parseBandwidth(bandwidth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
connConfig := &tcp.ConnectorConfig{
|
||||
ServerAddr: serverAddr,
|
||||
Token: token,
|
||||
@@ -85,6 +98,7 @@ func runHTTPS(_ *cobra.Command, args []string) error {
|
||||
AuthPass: authPass,
|
||||
AuthBearer: authBearer,
|
||||
Transport: parseTransport(transport),
|
||||
Bandwidth: bw,
|
||||
}
|
||||
|
||||
var daemon *DaemonInfo
|
||||
|
||||
@@ -313,6 +313,24 @@ func runServer(cmd *cobra.Command, _ []string) error {
|
||||
listener.SetAllowedTransports(cfg.AllowedTransports)
|
||||
listener.SetAllowedTunnelTypes(cfg.AllowedTunnelTypes)
|
||||
|
||||
bandwidth, err := parseBandwidth(cfg.Bandwidth)
|
||||
if err != nil {
|
||||
logger.Fatal("Invalid bandwidth configuration", zap.Error(err))
|
||||
}
|
||||
burstMultiplier := cfg.BurstMultiplier
|
||||
if burstMultiplier <= 0 {
|
||||
burstMultiplier = 2.0
|
||||
}
|
||||
listener.SetBandwidth(bandwidth)
|
||||
listener.SetBurstMultiplier(burstMultiplier)
|
||||
if bandwidth > 0 {
|
||||
logger.Info("Bandwidth limit configured",
|
||||
zap.String("bandwidth", cfg.Bandwidth),
|
||||
zap.Int64("bandwidth_bytes_sec", bandwidth),
|
||||
zap.Float64("burst_multiplier", burstMultiplier),
|
||||
)
|
||||
}
|
||||
|
||||
if err := listener.Start(); err != nil {
|
||||
logger.Fatal("Failed to start TCP listener", zap.Error(err))
|
||||
}
|
||||
|
||||
@@ -242,6 +242,7 @@ func buildConnectorConfig(cfg *config.ClientConfig, t *config.TunnelConfig) *tcp
|
||||
AuthPass: t.Auth,
|
||||
AuthBearer: t.AuthBearer,
|
||||
Transport: transport,
|
||||
Bandwidth: parseBandwidthOrZero(t.Bandwidth),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,3 +252,8 @@ func getAddress(t *config.TunnelConfig) string {
|
||||
}
|
||||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
func parseBandwidthOrZero(s string) int64 {
|
||||
bw, _ := parseBandwidth(s)
|
||||
return bw
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ Example:
|
||||
drip tcp 22 --allow-ip 10.0.0.1 Allow single IP
|
||||
drip tcp 22 --deny-ip 1.2.3.4 Block specific IP
|
||||
drip tcp 22 --transport wss Use WebSocket over TLS (CDN-friendly)
|
||||
drip tcp 22 --bandwidth 1M Limit bandwidth to 1 MB/s
|
||||
|
||||
Supported Services:
|
||||
- Databases: PostgreSQL (5432), MySQL (3306), Redis (6379), MongoDB (27017)
|
||||
@@ -54,6 +55,7 @@ func init() {
|
||||
tcpCmd.Flags().StringSliceVar(&allowIPs, "allow-ip", nil, "Allow only these IPs or CIDR ranges (e.g., 192.168.1.1,10.0.0.0/8)")
|
||||
tcpCmd.Flags().StringSliceVar(&denyIPs, "deny-ip", nil, "Deny these IPs or CIDR ranges (e.g., 1.2.3.4,192.168.1.0/24)")
|
||||
tcpCmd.Flags().StringVar(&transport, "transport", "auto", "Transport protocol: auto, tcp, wss (WebSocket over TLS)")
|
||||
tcpCmd.Flags().StringVar(&bandwidth, "bandwidth", "", "Bandwidth limit (e.g., 1M, 500K, 1G)")
|
||||
tcpCmd.Flags().BoolVar(&daemonMarker, "daemon-child", false, "Internal flag for daemon child process")
|
||||
tcpCmd.Flags().MarkHidden("daemon-child")
|
||||
rootCmd.AddCommand(tcpCmd)
|
||||
@@ -74,6 +76,11 @@ func runTCP(_ *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
bw, err := parseBandwidth(bandwidth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
connConfig := &tcp.ConnectorConfig{
|
||||
ServerAddr: serverAddr,
|
||||
Token: token,
|
||||
@@ -85,6 +92,7 @@ func runTCP(_ *cobra.Command, args []string) error {
|
||||
AllowIPs: allowIPs,
|
||||
DenyIPs: denyIPs,
|
||||
Transport: parseTransport(transport),
|
||||
Bandwidth: bw,
|
||||
}
|
||||
|
||||
var daemon *DaemonInfo
|
||||
|
||||
@@ -30,6 +30,9 @@ func buildDaemonArgs(tunnelType string, args []string, subdomain string, localAd
|
||||
if authBearer != "" {
|
||||
daemonArgs = append(daemonArgs, "--auth-bearer", authBearer)
|
||||
}
|
||||
if bandwidth != "" {
|
||||
daemonArgs = append(daemonArgs, "--bandwidth", bandwidth)
|
||||
}
|
||||
if insecure {
|
||||
daemonArgs = append(daemonArgs, "--insecure")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user